diff --git a/document.go b/document.go
index acdca950..9f908d35 100644
--- a/document.go
+++ b/document.go
@@ -14,7 +14,6 @@
 package libopenapi
 
 import (
-	"errors"
 	"fmt"
 
 	"github.com/pb33f/libopenapi/index"
@@ -48,13 +47,13 @@ type Document interface {
 	// If there are any issues, then no model will be returned, instead a slice of errors will explain all the
 	// problems that occurred. This method will only support version 2 specifications and will throw an error for
 	// any other types.
-	BuildV2Model() (*DocumentModel[v2high.Swagger], []error)
+	BuildV2Model() (*DocumentModel[v2high.Swagger], error)
 
 	// BuildV3Model will build out an OpenAPI (version 3+) model from the specification used to create the document
 	// If there are any issues, then no model will be returned, instead a slice of errors will explain all the
 	// problems that occurred. This method will only support version 3 specifications and will throw an error for
 	// any other types.
-	BuildV3Model() (*DocumentModel[v3high.Document], []error)
+	BuildV3Model() (*DocumentModel[v3high.Document], error)
 
 	// RenderAndReload will render the high level model as it currently exists (including any mutations, additions
 	// and removals to and from any object in the tree). It will then reload the low level model with the new bytes
@@ -70,7 +69,7 @@ type Document interface {
 	// **IMPORTANT** This method only supports OpenAPI Documents. The Swagger model will not support mutations correctly
 	// and will not update when called. This choice has been made because we don't want to continue supporting Swagger,
 	// it's too old, so it should be motivation to upgrade to OpenAPI 3.
-	RenderAndReload() ([]byte, Document, *DocumentModel[v3high.Document], []error)
+	RenderAndReload() ([]byte, Document, *DocumentModel[v3high.Document], error)
 
 	// Serialize will re-render a Document back into a []byte slice. If any modifications have been made to the
 	// underlying data model using low level APIs, then those changes will be reflected in the serialized output.
@@ -156,9 +155,9 @@ func (d *document) Serialize() ([]byte, error) {
 	}
 }
 
-func (d *document) RenderAndReload() ([]byte, Document, *DocumentModel[v3high.Document], []error) {
+func (d *document) RenderAndReload() ([]byte, Document, *DocumentModel[v3high.Document], error) {
 	if d.highSwaggerModel != nil && d.highOpenAPI3Model == nil {
-		return nil, nil, nil, []error{errors.New("this method only supports OpenAPI 3 documents, not Swagger")}
+		return nil, nil, nil, errorMsg("this method only supports OpenAPI 3 documents, not Swagger")
 	}
 
 	var newBytes []byte
@@ -181,30 +180,27 @@ func (d *document) RenderAndReload() ([]byte, Document, *DocumentModel[v3high.Do
 
 	newDoc, err := NewDocumentWithConfiguration(newBytes, d.config)
 	if err != nil {
-		return newBytes, newDoc, nil, []error{err}
+		return newBytes, newDoc, nil, wrapErr(err)
 	}
 	// build the model.
-	model, errs := newDoc.BuildV3Model()
-	if errs != nil {
-		return newBytes, newDoc, model, errs
+	model, err := newDoc.BuildV3Model()
+	if err != nil {
+		return newBytes, newDoc, model, wrapErr(err)
 	}
 	// this document is now dead, long live the new document!
 	return newBytes, newDoc, model, nil
 }
 
-func (d *document) BuildV2Model() (*DocumentModel[v2high.Swagger], []error) {
+func (d *document) BuildV2Model() (*DocumentModel[v2high.Swagger], error) {
 	if d.highSwaggerModel != nil {
 		return d.highSwaggerModel, nil
 	}
-	var errors []error
 	if d.info == nil {
-		errors = append(errors, fmt.Errorf("unable to build swagger document, no specification has been loaded"))
-		return nil, errors
+		return nil, errorMsg("unable to build swagger document, no specification has been loaded")
 	}
 	if d.info.SpecFormat != datamodel.OAS2 {
-		errors = append(errors, fmt.Errorf("unable to build swagger document, "+
-			"supplied spec is a different version (%v). Try 'BuildV3Model()'", d.info.SpecFormat))
-		return nil, errors
+		return nil, errorMsgf("unable to build swagger document, "+
+			"supplied spec is a different version (%v). Try 'BuildV3Model()'", d.info.SpecFormat)
 	}
 
 	var lowDoc *v2low.Swagger
@@ -215,16 +211,16 @@ func (d *document) BuildV2Model() (*DocumentModel[v2high.Swagger], []error) {
 		}
 	}
 
-	lowDoc, errors = v2low.CreateDocumentFromConfig(d.info, d.config)
+	lowDoc, errs := v2low.CreateDocumentFromConfig(d.info, d.config)
 	// Do not short-circuit on circular reference errors, so the client
 	// has the option of ignoring them.
-	for _, err := range errors {
+	for _, err := range errs {
 		if refErr, ok := err.(*resolver.ResolvingError); ok {
 			if refErr.CircularReference == nil {
-				return nil, errors
+				return nil, wrapErrs(errs)
 			}
 		} else {
-			return nil, errors
+			return nil, wrapErrs(errs)
 		}
 	}
 	highDoc := v2high.NewSwaggerDocument(lowDoc)
@@ -232,22 +228,19 @@ func (d *document) BuildV2Model() (*DocumentModel[v2high.Swagger], []error) {
 		Model: *highDoc,
 		Index: lowDoc.Index,
 	}
-	return d.highSwaggerModel, errors
+	return d.highSwaggerModel, wrapErrs(errs)
 }
 
-func (d *document) BuildV3Model() (*DocumentModel[v3high.Document], []error) {
+func (d *document) BuildV3Model() (*DocumentModel[v3high.Document], error) {
 	if d.highOpenAPI3Model != nil {
 		return d.highOpenAPI3Model, nil
 	}
-	var errors []error
 	if d.info == nil {
-		errors = append(errors, fmt.Errorf("unable to build document, no specification has been loaded"))
-		return nil, errors
+		return nil, errorMsg("unable to build document, no specification has been loaded")
 	}
 	if d.info.SpecFormat != datamodel.OAS3 {
-		errors = append(errors, fmt.Errorf("unable to build openapi document, "+
-			"supplied spec is a different version (%v). Try 'BuildV2Model()'", d.info.SpecFormat))
-		return nil, errors
+		return nil, errorMsgf("unable to build openapi document, "+
+			"supplied spec is a different version (%v). Try 'BuildV2Model()'", d.info.SpecFormat)
 	}
 
 	var lowDoc *v3low.Document
@@ -258,16 +251,16 @@ func (d *document) BuildV3Model() (*DocumentModel[v3high.Document], []error) {
 		}
 	}
 
-	lowDoc, errors = v3low.CreateDocumentFromConfig(d.info, d.config)
+	lowDoc, errs := v3low.CreateDocumentFromConfig(d.info, d.config)
 	// Do not short-circuit on circular reference errors, so the client
 	// has the option of ignoring them.
-	for _, err := range errors {
+	for _, err := range errs {
 		if refErr, ok := err.(*resolver.ResolvingError); ok {
 			if refErr.CircularReference == nil {
-				return nil, errors
+				return nil, wrapErrs(errs)
 			}
 		} else {
-			return nil, errors
+			return nil, wrapErrs(errs)
 		}
 	}
 	highDoc := v3high.NewDocument(lowDoc)
@@ -275,7 +268,7 @@ func (d *document) BuildV3Model() (*DocumentModel[v3high.Document], []error) {
 		Model: *highDoc,
 		Index: lowDoc.Index,
 	}
-	return d.highOpenAPI3Model, errors
+	return d.highOpenAPI3Model, wrapErrs(errs)
 }
 
 // CompareDocuments will accept a left and right Document implementing struct, build a model for the correct
@@ -284,37 +277,40 @@ func (d *document) BuildV3Model() (*DocumentModel[v3high.Document], []error) {
 // If there are any errors when building the models, those errors are returned with a nil pointer for the
 // model.DocumentChanges. If there are any changes found however between either Document, then a pointer to
 // model.DocumentChanges is returned containing every single change, broken down, model by model.
-func CompareDocuments(original, updated Document) (*model.DocumentChanges, []error) {
-	var errors []error
+func CompareDocuments(original, updated Document) (*model.DocumentChanges, error) {
+	errs := &MultiError{}
 	if original.GetSpecInfo().SpecType == utils.OpenApi3 && updated.GetSpecInfo().SpecType == utils.OpenApi3 {
-		v3ModelLeft, errs := original.BuildV3Model()
-		if len(errs) > 0 {
-			errors = errs
+		v3ModelLeft, err := original.BuildV3Model()
+		if err != nil {
+			errs.Append(err)
 		}
-		v3ModelRight, errs := updated.BuildV3Model()
-		if len(errs) > 0 {
-			errors = append(errors, errs...)
+		v3ModelRight, err := updated.BuildV3Model()
+		if err != nil {
+			errs.Append(err)
 		}
 		if v3ModelLeft != nil && v3ModelRight != nil {
-			return what_changed.CompareOpenAPIDocuments(v3ModelLeft.Model.GoLow(), v3ModelRight.Model.GoLow()), errors
+			return what_changed.CompareOpenAPIDocuments(v3ModelLeft.Model.GoLow(), v3ModelRight.Model.GoLow()),
+				errs.OrNil()
 		} else {
-			return nil, errors
+			return nil, errs.OrNil()
 		}
 	}
 	if original.GetSpecInfo().SpecType == utils.OpenApi2 && updated.GetSpecInfo().SpecType == utils.OpenApi2 {
-		v2ModelLeft, errs := original.BuildV2Model()
-		if len(errs) > 0 {
-			errors = errs
+		errs := &MultiError{}
+		v2ModelLeft, err := original.BuildV2Model()
+		if err != nil {
+			errs.Append(err)
 		}
-		v2ModelRight, errs := updated.BuildV2Model()
-		if len(errs) > 0 {
-			errors = append(errors, errs...)
+		v2ModelRight, err := updated.BuildV2Model()
+		if err != nil {
+			errs.Append(err)
 		}
 		if v2ModelLeft != nil && v2ModelRight != nil {
-			return what_changed.CompareSwaggerDocuments(v2ModelLeft.Model.GoLow(), v2ModelRight.Model.GoLow()), errors
+			return what_changed.CompareSwaggerDocuments(v2ModelLeft.Model.GoLow(), v2ModelRight.Model.GoLow()),
+				errs.OrNil()
 		} else {
-			return nil, errors
+			return nil, errs.OrNil()
 		}
 	}
-	return nil, []error{fmt.Errorf("unable to compare documents, one or both documents are not of the same version")}
+	return nil, errorMsg("unable to compare documents, one or both documents are not of the same version")
 }
diff --git a/document_examples_test.go b/document_examples_test.go
index 4c3600a2..1ea4b805 100644
--- a/document_examples_test.go
+++ b/document_examples_test.go
@@ -4,13 +4,14 @@
 package libopenapi
 
 import (
+	"errors"
 	"fmt"
-	"github.com/pb33f/libopenapi/datamodel"
 	"net/url"
 	"os"
 	"strings"
 	"testing"
 
+	"github.com/pb33f/libopenapi/datamodel"
 	"github.com/pb33f/libopenapi/datamodel/high"
 	v3high "github.com/pb33f/libopenapi/datamodel/high/v3"
 	low "github.com/pb33f/libopenapi/datamodel/low/base"
@@ -18,6 +19,7 @@ import (
 	"github.com/pb33f/libopenapi/resolver"
 	"github.com/pb33f/libopenapi/utils"
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 )
 
 func ExampleNewDocument_fromOpenAPI3Document() {
@@ -36,14 +38,16 @@ func ExampleNewDocument_fromOpenAPI3Document() {
 	}
 
 	// because we know this is a v3 spec, we can build a ready to go model from it.
-	v3Model, errors := document.BuildV3Model()
+	v3Model, err := document.BuildV3Model()
 
 	// if anything went wrong when building the v3 model, a slice of errors will be returned
-	if len(errors) > 0 {
-		for i := range errors {
-			fmt.Printf("error: %e\n", errors[i])
+	if err != nil {
+		var errs *MultiError
+		if errors.As(err, &errs) {
+			errs.Print()
+			panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", errs.Count()))
 		}
-		panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors)))
+		panic("error returned was not of type '*MultiError'")
 	}
 
 	// get a count of the number of paths and schemas.
@@ -78,10 +82,10 @@ func ExampleNewDocument_fromWithDocumentConfigurationFailure() {
 	}
 
 	// only errors will be thrown, so just capture them and print the number of errors.
-	_, errors := doc.BuildV3Model()
+	_, err = doc.BuildV3Model()
 
 	// if anything went wrong when building the v3 model, a slice of errors will be returned
-	if len(errors) > 0 {
+	if err != nil {
 		fmt.Println("Error building Digital Ocean spec errors reported")
 	}
 	// Output: Error building Digital Ocean spec errors reported
@@ -115,10 +119,10 @@ func ExampleNewDocument_fromWithDocumentConfigurationSuccess() {
 	}
 
 	// only errors will be thrown, so just capture them and print the number of errors.
-	_, errors := doc.BuildV3Model()
+	_, err = doc.BuildV3Model()
 
 	// if anything went wrong when building the v3 model, a slice of errors will be returned
-	if len(errors) > 0 {
+	if err != nil {
 		fmt.Println("Error building Digital Ocean spec errors reported")
 	} else {
 		fmt.Println("Digital Ocean spec built successfully")
@@ -142,14 +146,16 @@ func ExampleNewDocument_fromSwaggerDocument() {
 	}
 
 	// because we know this is a v2 spec, we can build a ready to go model from it.
-	v2Model, errors := document.BuildV2Model()
+	v2Model, err := document.BuildV2Model()
 
 	// if anything went wrong when building the v3 model, a slice of errors will be returned
-	if len(errors) > 0 {
-		for i := range errors {
-			fmt.Printf("error: %e\n", errors[i])
+	if err != nil {
+		var errs *MultiError
+		if errors.As(err, &errs) {
+			errs.Print()
+			panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", errs.Count()))
 		}
-		panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors)))
+		panic("error returned was not of type '*MultiError'")
 	}
 
 	// get a count of the number of paths and schemas.
@@ -175,36 +181,34 @@ func ExampleNewDocument_fromUnknownVersion() {
 	}
 
 	var paths, schemas int
-	var errors []error
+	errs := MultiError{}
 
 	// We don't know which type of document this is, so we can use the spec info to inform us
 	if document.GetSpecInfo().SpecType == utils.OpenApi3 {
-		v3Model, errs := document.BuildV3Model()
-		if len(errs) > 0 {
-			errors = errs
+		v3Model, err := document.BuildV3Model()
+		if err != nil {
+			errs.Append(err)
 		}
-		if len(errors) <= 0 {
+		if errs.Count() <= 0 {
 			paths = len(v3Model.Model.Paths.PathItems)
 			schemas = len(v3Model.Model.Components.Schemas)
 		}
 	}
 	if document.GetSpecInfo().SpecType == utils.OpenApi2 {
-		v2Model, errs := document.BuildV2Model()
-		if len(errs) > 0 {
-			errors = errs
+		v2Model, err := document.BuildV2Model()
+		if err != nil {
+			errs.Append(err)
 		}
-		if len(errors) <= 0 {
+		if errs.Count() <= 0 {
 			paths = len(v2Model.Model.Paths.PathItems)
 			schemas = len(v2Model.Model.Definitions.Definitions)
 		}
 	}
 
 	// if anything went wrong when building the model, report errors.
-	if len(errors) > 0 {
-		for i := range errors {
-			fmt.Printf("error: %e\n", errors[i])
-		}
-		panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors)))
+	if errs.Count() > 0 {
+		errs.Print()
+		panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", errs.Count()))
 	}
 
 	// print the number of paths and schemas in the document
@@ -236,14 +240,16 @@ info:
 	}
 
 	// because we know this is a v3 spec, we can build a ready to go model from it.
-	v3Model, errors := document.BuildV3Model()
+	v3Model, err := document.BuildV3Model()
 
 	// if anything went wrong when building the v3 model, a slice of errors will be returned
-	if len(errors) > 0 {
-		for i := range errors {
-			fmt.Printf("error: %e\n", errors[i])
+	if err != nil {
+		var errs *MultiError
+		if errors.As(err, &errs) {
+			errs.Print()
+			panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", errs.Count()))
 		}
-		panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors)))
+		panic("error returned was not of type '*MultiError'")
 	}
 
 	// mutate the title, to do this we currently need to drop down to the low-level API.
@@ -305,14 +311,16 @@ func ExampleCompareDocuments_openAPI() {
 	}
 
 	// Compare documents for all changes made
-	documentChanges, errs := CompareDocuments(originalDoc, updatedDoc)
+	documentChanges, err := CompareDocuments(originalDoc, updatedDoc)
 
 	// If anything went wrong when building models for documents.
-	if len(errs) > 0 {
-		for i := range errs {
-			fmt.Printf("error: %e\n", errs[i])
+	if err != nil {
+		var errs *MultiError
+		if errors.As(err, &errs) {
+			errs.Print()
+			panic(fmt.Sprintf("cannot compare documents: %d errors reported", errs.Count()))
 		}
-		panic(fmt.Sprintf("cannot compare documents: %d errors reported", len(errs)))
+		panic("error returned was not of type '*MultiError'")
 	}
 
 	// Extract SchemaChanges from components changes.
@@ -352,14 +360,16 @@ func ExampleCompareDocuments_swagger() {
 	}
 
 	// Compare documents for all changes made
-	documentChanges, errs := CompareDocuments(originalDoc, updatedDoc)
+	documentChanges, err := CompareDocuments(originalDoc, updatedDoc)
 
 	// If anything went wrong when building models for documents.
-	if len(errs) > 0 {
-		for i := range errs {
-			fmt.Printf("error: %e\n", errs[i])
+	if err != nil {
+		var errs *MultiError
+		if errors.As(err, &errs) {
+			errs.Print()
+			panic(fmt.Sprintf("cannot compare documents: %d errors reported", errs.Count()))
 		}
-		panic(fmt.Sprintf("cannot compare documents: %d errors reported", len(errs)))
+		panic("error returned was not of type '*MultiError'")
 	}
 
 	// Extract SchemaChanges from components changes.
@@ -426,10 +436,16 @@ components:
 	if err != nil {
 		panic(fmt.Sprintf("cannot create new document: %e", err))
 	}
-	_, errs := doc.BuildV3Model()
+	_, err = doc.BuildV3Model()
 
 	// extract resolving error
-	resolvingError := errs[0]
+	var errs *MultiError
+	var resolvingError error
+	if errors.As(err, &errs) {
+		resolvingError = errs.Unwrap()[0]
+	} else {
+		panic("error returned was not of type '*MultiError'")
+	}
 
 	// resolving error is a pointer to *resolver.ResolvingError
 	// which provides access to rich details about the error.
@@ -475,12 +491,9 @@ components:
 	doc, err := NewDocument([]byte(spec))
 
 	// if anything went wrong, an error is thrown
-	if err != nil {
-		panic(fmt.Sprintf("cannot create new document: %e", err))
-	}
-	_, errs := doc.BuildV3Model()
-
-	assert.Len(t, errs, 0)
+	require.NoError(t, err)
+	_, err = doc.BuildV3Model()
+	assert.NoError(t, err)
 }
 
 // If you're using complex types with OpenAPI Extensions, it's simple to unpack extensions into complex
@@ -618,14 +631,15 @@ func ExampleNewDocument_modifyAndReRender() {
 	}
 
 	// because we know this is a v3 spec, we can build a ready to go model from it.
-	v3Model, errors := doc.BuildV3Model()
+	v3Model, err := doc.BuildV3Model()
 
-	// if anything went wrong when building the v3 model, a slice of errors will be returned
-	if len(errors) > 0 {
-		for i := range errors {
-			fmt.Printf("error: %e\n", errors[i])
+	if err != nil {
+		var errs *MultiError
+		if errors.As(err, &errs) {
+			errs.Print()
+			panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", errs.Count()))
 		}
-		panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors)))
+		panic("error returned was not of type '*MultiError'")
 	}
 
 	// create a new path item and operation.
@@ -647,16 +661,20 @@ func ExampleNewDocument_modifyAndReRender() {
 	v3Model.Model.Paths.PathItems["/new/path"] = newPath
 
 	// render the document back to bytes and reload the model.
-	rawBytes, _, newModel, errs := doc.RenderAndReload()
-
-	// if anything went wrong when re-rendering the v3 model, a slice of errors will be returned
-	if len(errors) > 0 {
-		panic(fmt.Sprintf("cannot re-render document: %d errors reported", len(errs)))
-	}
+	rawBytes, _, newModel, err := doc.RenderAndReload()
 
 	// capture new number of paths after re-rendering
 	newPaths := len(newModel.Model.Paths.PathItems)
 
+	// if anything went wrong when re-rendering the v3 model, a slice of errors will be returned
+	if err != nil {
+		var errs *MultiError
+		if errors.As(err, &errs) {
+			panic(fmt.Sprintf("cannot re-render document: %d errors reported", errs.Count()))
+		}
+		panic("error returned was not of type '*MultiError'")
+	}
+
 	// print the number of paths and schemas in the document
 	fmt.Printf("There were %d original paths. There are now %d paths in the document\n", originalPaths, newPaths)
 	fmt.Printf("The original spec had %d bytes, the new one has %d\n", len(petstore), len(rawBytes))
diff --git a/document_test.go b/document_test.go
index f2f66208..43c3bc89 100644
--- a/document_test.go
+++ b/document_test.go
@@ -3,6 +3,7 @@
 package libopenapi
 
 import (
+	"errors"
 	"fmt"
 	"github.com/pb33f/libopenapi/datamodel"
 	"github.com/pb33f/libopenapi/datamodel/high/base"
@@ -22,7 +23,7 @@ func TestLoadDocument_Simple_V2(t *testing.T) {
 	assert.Equal(t, "2.0.1", doc.GetVersion())
 
 	v2Doc, docErr := doc.BuildV2Model()
-	assert.Len(t, docErr, 0)
+	assert.NoError(t, docErr)
 	assert.NotNil(t, v2Doc)
 	assert.NotNil(t, doc.GetSpecInfo())
 
@@ -37,7 +38,9 @@ func TestLoadDocument_Simple_V2_Error(t *testing.T) {
 	assert.NoError(t, err)
 
 	v2Doc, docErr := doc.BuildV3Model()
-	assert.Len(t, docErr, 1)
+	var errs *MultiError
+	assert.True(t, errors.As(docErr, &errs))
+	assert.Equal(t, errs.Count(), 1)
 	assert.Nil(t, v2Doc)
 }
 
@@ -51,7 +54,9 @@ definitions:
 	assert.NoError(t, err)
 
 	v2Doc, docErr := doc.BuildV2Model()
-	assert.Len(t, docErr, 2)
+	var errs *MultiError
+	assert.True(t, errors.As(docErr, &errs))
+	assert.Equal(t, errs.Count(), 2)
 	assert.Nil(t, v2Doc)
 }
 
@@ -62,7 +67,9 @@ func TestLoadDocument_Simple_V3_Error(t *testing.T) {
 	assert.NoError(t, err)
 
 	v2Doc, docErr := doc.BuildV2Model()
-	assert.Len(t, docErr, 1)
+	var errs *MultiError
+	assert.True(t, errors.As(docErr, &errs))
+	assert.Equal(t, errs.Count(), 1)
 	assert.Nil(t, v2Doc)
 }
 
@@ -70,14 +77,18 @@ func TestLoadDocument_Error_V2NoSpec(t *testing.T) {
 
 	doc := new(document) // not how this should be instantiated.
 	_, err := doc.BuildV2Model()
-	assert.Len(t, err, 1)
+	var errs *MultiError
+	assert.True(t, errors.As(err, &errs))
+	assert.Equal(t, errs.Count(), 1)
 }
 
 func TestLoadDocument_Error_V3NoSpec(t *testing.T) {
 
 	doc := new(document) // not how this should be instantiated.
 	_, err := doc.BuildV3Model()
-	assert.Len(t, err, 1)
+	var errs *MultiError
+	assert.True(t, errors.As(err, &errs))
+	assert.Equal(t, errs.Count(), 1)
 }
 
 func TestLoadDocument_Empty(t *testing.T) {
@@ -94,7 +105,7 @@ func TestLoadDocument_Simple_V3(t *testing.T) {
 	assert.Equal(t, "3.0.1", doc.GetVersion())
 
 	v3Doc, docErr := doc.BuildV3Model()
-	assert.Len(t, docErr, 0)
+	assert.NoError(t, docErr)
 	assert.NotNil(t, v3Doc)
 }
 
@@ -108,7 +119,9 @@ paths:
 	assert.NoError(t, err)
 
 	v3Doc, docErr := doc.BuildV3Model()
-	assert.Len(t, docErr, 2)
+	var errs *MultiError
+	assert.True(t, errors.As(docErr, &errs))
+	assert.Equal(t, errs.Count(), 2)
 	assert.Nil(t, v3Doc)
 }
 
@@ -161,10 +174,10 @@ func TestDocument_RenderAndReload_ChangeCheck_Burgershop(t *testing.T) {
 	rend, newDoc, _, _ := doc.RenderAndReload()
 
 	// compare documents
-	compReport, errs := CompareDocuments(doc, newDoc)
+	compReport, err := CompareDocuments(doc, newDoc)
 
-	// should noth be nil.
-	assert.Nil(t, errs)
+	// should both be nil.
+	assert.NoError(t, err)
 	assert.NotNil(t, rend)
 	assert.Nil(t, compReport)
 
@@ -179,7 +192,7 @@ func TestDocument_RenderAndReload_ChangeCheck_Stripe(t *testing.T) {
 	_, newDoc, _, _ := doc.RenderAndReload()
 
 	// compare documents
-	compReport, errs := CompareDocuments(doc, newDoc)
+	compReport, err := CompareDocuments(doc, newDoc)
 
 	// get flat list of changes.
 	flatChanges := compReport.GetAllChanges()
@@ -192,7 +205,7 @@ func TestDocument_RenderAndReload_ChangeCheck_Stripe(t *testing.T) {
 		}
 	}
 
-	assert.Nil(t, errs)
+	assert.NoError(t, err)
 	tc := compReport.TotalChanges()
 	bc := compReport.TotalBreakingChanges()
 	assert.Equal(t, 0, bc)
@@ -213,12 +226,12 @@ func TestDocument_RenderAndReload_ChangeCheck_Asana(t *testing.T) {
 	assert.NotNil(t, dat)
 
 	// compare documents
-	compReport, errs := CompareDocuments(doc, newDoc)
+	compReport, err := CompareDocuments(doc, newDoc)
 
 	// get flat list of changes.
 	flatChanges := compReport.GetAllChanges()
 
-	assert.Nil(t, errs)
+	assert.Nil(t, err)
 	tc := compReport.TotalChanges()
 	assert.Equal(t, 21, tc)
 
@@ -283,9 +296,11 @@ func TestDocument_RenderAndReload_Swagger(t *testing.T) {
 	doc, _ := NewDocument(petstore)
 	doc.BuildV2Model()
 	doc.BuildV2Model()
-	_, _, _, e := doc.RenderAndReload()
-	assert.Len(t, e, 1)
-	assert.Equal(t, "this method only supports OpenAPI 3 documents, not Swagger", e[0].Error())
+	_, _, _, err := doc.RenderAndReload()
+	var errs *MultiError
+	assert.True(t, errors.As(err, &errs))
+	assert.Equal(t, errs.Count(), 1)
+	assert.Equal(t, "this method only supports OpenAPI 3 documents, not Swagger", errs.Unwrap()[0].Error())
 
 }
 
@@ -294,24 +309,28 @@ func TestDocument_BuildModelPreBuild(t *testing.T) {
 	doc, _ := NewDocument(petstore)
 	doc.BuildV3Model()
 	doc.BuildV3Model()
-	_, _, _, e := doc.RenderAndReload()
-	assert.Len(t, e, 0)
+	_, _, _, err := doc.RenderAndReload()
+	assert.NoError(t, err)
 }
 
 func TestDocument_BuildModelCircular(t *testing.T) {
 	petstore, _ := ioutil.ReadFile("test_specs/circular-tests.yaml")
 	doc, _ := NewDocument(petstore)
-	m, e := doc.BuildV3Model()
+	m, err := doc.BuildV3Model()
 	assert.NotNil(t, m)
-	assert.Len(t, e, 3)
+	var errs *MultiError
+	assert.True(t, errors.As(err, &errs))
+	assert.Equal(t, errs.Count(), 3)
 }
 
 func TestDocument_BuildModelBad(t *testing.T) {
 	petstore, _ := ioutil.ReadFile("test_specs/badref-burgershop.openapi.yaml")
 	doc, _ := NewDocument(petstore)
-	m, e := doc.BuildV3Model()
+	m, err := doc.BuildV3Model()
 	assert.Nil(t, m)
-	assert.Len(t, e, 9)
+	var errs *MultiError
+	assert.True(t, errors.As(err, &errs))
+	assert.Equal(t, errs.Count(), 9)
 }
 
 func TestDocument_Serialize_JSON_Modified(t *testing.T) {
@@ -357,9 +376,9 @@ paths:
 		panic(err)
 	}
 
-	result, errs := doc.BuildV3Model()
-	if len(errs) > 0 {
-		panic(errs)
+	result, err := doc.BuildV3Model()
+	if err != nil {
+		panic(err)
 	}
 
 	// extract operation.
@@ -376,8 +395,10 @@ func TestDocument_BuildModel_CompareDocsV3_LeftError(t *testing.T) {
 	burgerShopUpdated, _ := ioutil.ReadFile("test_specs/burgershop.openapi-modified.yaml")
 	originalDoc, _ := NewDocument(burgerShopOriginal)
 	updatedDoc, _ := NewDocument(burgerShopUpdated)
-	changes, errors := CompareDocuments(originalDoc, updatedDoc)
-	assert.Len(t, errors, 9)
+	changes, err := CompareDocuments(originalDoc, updatedDoc)
+	var errs *MultiError
+	assert.True(t, errors.As(err, &errs))
+	assert.Equal(t, errs.Count(), 9)
 	assert.Nil(t, changes)
 }
 
@@ -387,8 +408,10 @@ func TestDocument_BuildModel_CompareDocsV3_RightError(t *testing.T) {
 	burgerShopUpdated, _ := ioutil.ReadFile("test_specs/burgershop.openapi-modified.yaml")
 	originalDoc, _ := NewDocument(burgerShopOriginal)
 	updatedDoc, _ := NewDocument(burgerShopUpdated)
-	changes, errors := CompareDocuments(updatedDoc, originalDoc)
-	assert.Len(t, errors, 9)
+	changes, err := CompareDocuments(updatedDoc, originalDoc)
+	var errs *MultiError
+	assert.True(t, errors.As(err, &errs))
+	assert.Equal(t, errs.Count(), 9)
 	assert.Nil(t, changes)
 
 }
@@ -399,8 +422,10 @@ func TestDocument_BuildModel_CompareDocsV2_Error(t *testing.T) {
 	burgerShopUpdated, _ := ioutil.ReadFile("test_specs/petstorev2-badref.json")
 	originalDoc, _ := NewDocument(burgerShopOriginal)
 	updatedDoc, _ := NewDocument(burgerShopUpdated)
-	changes, errors := CompareDocuments(updatedDoc, originalDoc)
-	assert.Len(t, errors, 2)
+	changes, err := CompareDocuments(updatedDoc, originalDoc)
+	var errs *MultiError
+	assert.True(t, errors.As(err, &errs))
+	assert.Equal(t, errs.Count(), 2)
 	assert.Nil(t, changes)
 
 }
@@ -411,8 +436,10 @@ func TestDocument_BuildModel_CompareDocsV2V3Mix_Error(t *testing.T) {
 	burgerShopUpdated, _ := ioutil.ReadFile("test_specs/petstorev3.json")
 	originalDoc, _ := NewDocument(burgerShopOriginal)
 	updatedDoc, _ := NewDocument(burgerShopUpdated)
-	changes, errors := CompareDocuments(updatedDoc, originalDoc)
-	assert.Len(t, errors, 1)
+	changes, err := CompareDocuments(updatedDoc, originalDoc)
+	var errs *MultiError
+	assert.True(t, errors.As(err, &errs))
+	assert.Equal(t, errs.Count(), 1)
 	assert.Nil(t, changes)
 
 }
@@ -429,14 +456,15 @@ func TestSchemaRefIsFollowed(t *testing.T) {
 	}
 
 	// because we know this is a v3 spec, we can build a ready to go model from it.
-	v3Model, errors := document.BuildV3Model()
+	v3Model, err := document.BuildV3Model()
 
-	// if anything went wrong when building the v3 model, a slice of errors will be returned
-	if len(errors) > 0 {
-		for i := range errors {
-			fmt.Printf("error: %e\n", errors[i])
+	// if anything went wrong when building the v3 model, a MultiError will be returned
+	if err != nil {
+		var errs *MultiError
+		if errors.As(err, &errs) {
+			errs.Print()
 		}
-		panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors)))
+		panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", errs.Count()))
 	}
 
 	// get a count of the number of paths and schemas.
@@ -494,9 +522,9 @@ paths:
 		panic(err)
 	}
 
-	result, errs := doc.BuildV3Model()
-	if len(errs) > 0 {
-		panic(errs)
+	result, err := doc.BuildV3Model()
+	if err != nil {
+		panic(err)
 	}
 
 	// render the document.
@@ -525,9 +553,9 @@ paths:
 		panic(err)
 	}
 
-	result, errs := doc.BuildV3Model()
-	if len(errs) > 0 {
-		panic(errs)
+	result, err := doc.BuildV3Model()
+	if err != nil {
+		panic(err)
 	}
 
 	assert.Equal(t, "crs", result.Model.Paths.PathItems["/test"].Get.Parameters[0].Name)
@@ -555,9 +583,9 @@ components:
 		panic(err)
 	}
 
-	result, errs := doc.BuildV3Model()
-	if len(errs) > 0 {
-		panic(errs)
+	result, err := doc.BuildV3Model()
+	if err != nil {
+		panic(err)
 	}
 
 	// render the document.
@@ -586,9 +614,9 @@ paths:
 		panic(err)
 	}
 
-	result, errs := doc.BuildV3Model()
-	if len(errs) > 0 {
-		panic(errs)
+	result, err := doc.BuildV3Model()
+	if err != nil {
+		panic(err)
 	}
 
 	// render the document.
diff --git a/error.go b/error.go
new file mode 100644
index 00000000..f8cfbd82
--- /dev/null
+++ b/error.go
@@ -0,0 +1,85 @@
+// Copyright 2023 Princess B33f Heavy Industries
+// SPDX-License-Identifier: MIT
+
+package libopenapi
+
+import (
+	"errors"
+	"fmt"
+	"strings"
+)
+
+func errorMsg(msg string) *MultiError {
+	return &MultiError{errs: []error{errors.New(msg)}}
+}
+
+func errorMsgf(msg string, a ...any) *MultiError {
+	return &MultiError{errs: []error{fmt.Errorf(msg, a...)}}
+}
+
+func wrapErr(err error) error {
+	if err == nil {
+		return nil
+	}
+	return &MultiError{errs: []error{err}}
+}
+
+func wrapErrs(err []error) error {
+	if len(err) == 0 {
+		return nil
+	}
+	return &MultiError{err}
+}
+
+type MultiError struct {
+	errs []error
+}
+
+func (e *MultiError) Append(err error) {
+	if err == nil {
+		return
+	}
+
+	var m *MultiError
+	if errors.As(err, &m) {
+		e.errs = append(e.errs, m.errs...)
+		return
+	}
+	e.errs = append(e.errs, err)
+}
+
+func (e *MultiError) Count() int {
+	return len(e.errs)
+}
+
+func (e *MultiError) Error() string {
+	var b strings.Builder
+	for i, err := range e.errs {
+		if err == nil {
+			b.WriteString(fmt.Sprintf("[%d] nil\n", i))
+			continue
+		}
+		b.WriteString(fmt.Sprintf("[%d] %s\n", i, err.Error()))
+	}
+	return b.String()
+}
+
+func (e *MultiError) Unwrap() []error {
+	return e.errs
+}
+
+// OrNil returns this instance of *MultiError or nil if there are no errors
+// This is useful because returning a &MultiError{} even if it's empty is
+// still considered an error.
+func (e *MultiError) OrNil() error {
+	if len(e.errs) == 0 {
+		return nil
+	}
+	return e
+}
+
+func (e *MultiError) Print() {
+	for i, err := range e.errs {
+		fmt.Printf("[%d] %s\n", i, err.Error())
+	}
+}
diff --git a/error_test.go b/error_test.go
new file mode 100644
index 00000000..50d5c636
--- /dev/null
+++ b/error_test.go
@@ -0,0 +1,57 @@
+// Copyright 2023 Princess B33f Heavy Industries
+// SPDX-License-Identifier: MIT
+
+package libopenapi
+
+import (
+	"errors"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestMultiError(t *testing.T) {
+	err := &MultiError{}
+	err.Append(errors.New("error 1"))
+	err.Append(errors.New("error 2"))
+	err.Append(wrapErr(errors.New("error 3")))
+	assert.Equal(t, "[0] error 1\n[1] error 2\n[2] error 3\n", err.Error())
+}
+
+func TestMultiError_OrNil(t *testing.T) {
+	err := &MultiError{}
+	err.Append(errors.New("error 1"))
+	err.Append(errors.New("error 2"))
+
+	// Append does not add nil errors
+	nilErr := &MultiError{}
+	err.Append(wrapErr(nilErr.OrNil()))
+
+	assert.Equal(t, "[0] error 1\n[1] error 2\n", err.Error())
+}
+
+func TestMultiError_NilError(t *testing.T) {
+	// When nil error added to the list.
+	err := &MultiError{errs: []error{
+		errors.New("error 1"),
+		nil,
+		errors.New("error 2"),
+	}}
+
+	// Should output as 'nil'
+	assert.Equal(t, "[0] error 1\n[1] nil\n[2] error 2\n", err.Error())
+}
+
+func ExampleMultiError_Print() {
+	err := &MultiError{}
+	err.Append(errors.New("error 1"))
+	err.Append(errors.New("error 2"))
+	err.Append(errors.New("error 3"))
+
+	err.Print()
+
+	// Output:
+	// [0] error 1
+	// [1] error 2
+	// [2] error 3
+}
diff --git a/go.mod b/go.mod
index ae7eecff..ba8ef189 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module github.com/pb33f/libopenapi
 
-go 1.18
+go 1.20
 
 require (
 	github.com/stretchr/testify v1.8.0
diff --git a/go.sum b/go.sum
index 026d1375..67087946 100644
--- a/go.sum
+++ b/go.sum
@@ -58,8 +58,6 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
-github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk=
 github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -80,8 +78,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
-golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
diff --git a/index/find_component_test.go b/index/find_component_test.go
index 048b1041..1d40047c 100644
--- a/index/find_component_test.go
+++ b/index/find_component_test.go
@@ -5,6 +5,7 @@ package index
 
 import (
 	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
 	"gopkg.in/yaml.v3"
 	"os"
 	"testing"
@@ -137,7 +138,7 @@ paths:
 
 	// extract crs param from index
 	crsParam := index.GetMappedReferences()["https://schemas.opengis.net/ogcapi/features/part2/1.0/openapi/ogcapi-features-2.yaml#/components/parameters/crs"]
-	assert.NotNil(t, crsParam)
+	require.NotNil(t, crsParam)
 	assert.True(t, crsParam.IsRemote)
 	assert.Equal(t, "crs", crsParam.Node.Content[1].Value)
 	assert.Equal(t, "query", crsParam.Node.Content[3].Value)