hashicorp/go-multierror: Aggregating Multiple Go Errors, and When errors.Join Replaces It
A Go (golang) package for representing a list of errors as a single error.
At a glance
- What is it?
- go-multierror represents a list of errors as one error, with a concurrent Group, custom formatting and Append/Flatten/Prefix helpers. Its own README now points new projects at errors.Join, so the interesting question is which of its extras still earn a dependency.
- Who is it for?
- Adopt go-multierror when you need the Group pattern for collecting every error from concurrent goroutines, or a custom ErrorFormat, and you are already inside a HashiCorp-adjacent codebase where the dependency is present. Do not add it to a new project whose only need is combining two errors into one return value; the README itself recommends errors.Join there.
- Can I use it commercially?
- Yes, with conditions. MPL-2.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
- Is it still maintained?
- Yes. The repository last received commits 85 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem go-multierror solves in a Go error-returning API
Go functions return a single error. That is fine until a function does several things that can each fail independently, such as validating a config struct field by field or running a batch of cleanup steps. The idiomatic workaround is to return the first failure and drop the rest, which loses information the caller may need. go-multierror exists so a function can return an error that is actually a list. The README frames it as a package that "provides a mechanism for representing a list of error values as a single error", and it is deliberately unobtrusive: a caller who has never heard of the package still gets a readable error string, while a caller who knows about it can unwrap the list and inspect each entry. The intended audience is library and application authors who already have an error-returning surface and cannot change its signature. It is not a logging package, not a retry mechanism, and not a replacement for context cancellation.
How Append, ErrorOrNil and the Error type fit together
The central type is multierror.Error, which implements the error interface and holds a slice of errors in its Errors field. You build one with Append, which the README describes as behaving like the built-in append: the first argument may be nil, a *multierror.Error, or any other error, and Append normalises it. That matters because it lets you write a running accumulator without branching on whether anything has failed yet. When you are done, ErrorOrNil returns nil if nothing was collected, which keeps the common success path clean. Introspection goes through the standard library. The README states the package is compatible with errors.As, errors.Is and errors.Unwrap. There is one detail worth reading twice: Unwrap returns errors one at a time via chaining, unlike errors.Join, which implements the newer Unwrap() []error signature added in Go 1.20. So errors.As and errors.Is work against a multierror, but the traversal is a chain rather than a flat slice. The repository layout matches this design: append.go, flatten.go, format.go, group.go, multierror.go, prefix.go and sort.go each hold one concern, with a matching _test.go file beside it.
Installing go-multierror and building a first error list
Installation is a single go get, and the README states the package requires Go 1.13 or newer because it relies on error wrapping introduced in that release. If you are on an older toolchain, the README points at the v1.0.0 tag, which does not depend on Go 1.13 features. The compile errors you would see on an old toolchain are undefined: errors.As and undefined: errors.Is.
go get github.com/hashicorp/go-multierrorA first real use is accumulating failures from two steps and returning them as one error. The README gives this exact shape, where result starts as a plain error value and Append grows it:
var result error
if err := step1(); err != nil {
result = multierror.Append(result, err)
}
if err := step2(); err != nil {
result = multierror.Append(result, err)
}
return resultIf you prefer to control the nil case yourself, declare a *multierror.Error instead and finish with result.ErrorOrNil(), which returns an error only when errors were added. To read the list back, the README shows a type switch on *multierror.Error and then use of the merr.Errors field. Formatting is replaceable: assigning a function to result.ErrorFormat changes what Error() string produces, which the README demonstrates with a function that simply returns "errors!".
multierror.Group versus errgroup for concurrent error collection
This is the part of the package that errors.Join does not cover, and it is the strongest remaining reason to depend on go-multierror. The README contrasts the two directly. golang.org/x/sync/errgroup returns only the first error from Wait, so if three goroutines fail you learn about one of them. multierror.Group collects all of them. The README's own migration note shows what replacing it costs: you write an ErrorCollector struct with a sync.Mutex and an errs []error slice, lock on Add, and join the slice in Err. That is perhaps fifteen lines of concurrency code you now own and must test, including the race behaviour. The Makefile in the repository has a testrace target that runs the suite under -race, which is the kind of check you would want on hand-rolled collector code. Whether that trade is worth it depends on how often you actually need every error from a fan-out rather than the first one.
Where go-multierror is the wrong tool now
The README answers this itself, in a note at the top: as of Go 1.20 the standard library provides errors.Join, and for new projects the maintainers recommend using it. That is an unusually direct piece of guidance and it should be taken at face value. If your requirement is combining a handful of errors into one return value, errors.Join does it with no dependency and no version floor beyond Go 1.20. The README also lists what go-multierror adds beyond that: custom formatting, the Group pattern, and utility functions Append, Flatten and Prefix. A project that needs none of those three is carrying a module for nothing. The second limitation is subtler and bites during migration. Because Unwrap on a multierror chains one error at a time while errors.Join implements Unwrap() []error, code that walks the chain behaves differently depending on which produced the value. Any type assertion to *multierror.Error also fails silently against a joined error, falling through to the else branch. The README does not document a rollback path for that migration, so the safe approach is to convert call sites and their assertions together rather than one at a time.
Alternatives and the actual difference in approach
errors.Join is the primary alternative and the difference is architectural, not cosmetic. It returns an error whose Unwrap method yields a slice, so errors.Is and errors.As see all members in one traversal, and it has no exported concrete type to assert against, no ErrorFormat hook and no Group. golang.org/x/sync/errgroup is the other real alternative for concurrent work. Its Wait returns the first error only, which is the opposite policy from multierror.Group: errgroup stops at the first failure signal, multierror.Group keeps everything. If you want fail-fast cancellation, errgroup is the correct choice and go-multierror would be working against you. The README's migration section also sketches a hand-written collector over errors.Join for teams that want all-errors collection without the dependency. That option is genuinely viable; its cost is the mutex and the slice you now maintain. pkg/errors appears in related searches, but the README here does not describe it, so treat any comparison you read elsewhere as unverified.
Maintenance, licence and the cost of upgrading
The repository is not archived, and the last push was on 2026-07-06. That is recent enough that the code is receiving attention, but the README's own recommendation to use errors.Join for new projects tells you where the centre of gravity sits: this package is in a maintenance posture for existing users rather than a growth phase. The module declares go 1.13 in go.mod, so it will build on old toolchains, which is a real benefit for long-lived codebases pinned to an older Go. There are no retrieved releases to reason about, so pinning strategy has to come from your own module graph rather than from a changelog summary here. The licence is MPL-2.0, a file-level copyleft licence. Modifying files in the package carries obligations that importing it as an unmodified dependency does not. That is a general property of MPL-2.0, not legal advice, and if you plan to fork or patch the source you should have someone qualified read the licence text. The upgrade cost that matters most is not the version bump but the migration semantics described above.
Editorial conclusion
Adopt go-multierror when you need the Group pattern for collecting every error from concurrent goroutines, or a custom ErrorFormat, and you are already inside a HashiCorp-adjacent codebase where the dependency is present. Do not add it to a new project whose only need is combining two errors into one return value; the README itself recommends errors.Join there. Before committing, check two things in your own code: whether anything type-asserts to *multierror.Error, because that assertion breaks the moment the value is produced by errors.Join instead, and whether your callers rely on errors.Unwrap walking one error at a time, since errors.Join implements the newer Unwrap() []error signature.
Frequently asked questions
What is hashicorp/go-multierror used for?
It represents a list of Go error values as a single error, so a function that can fail in several independent ways can return all of those failures instead of just the first. The README notes it also adds custom formatting, the Group pattern for concurrent collection, and the Append, Flatten and Prefix helpers.
Should I use hashicorp/go-multierror or errors.Join?
The README recommends errors.Join from the standard library for new projects, since Go 1.20 added it. go-multierror remains the choice when you need custom formatting, the Group pattern, or the utility functions it provides.
What Go version does hashicorp/go-multierror require?
The README states it requires Go 1.13 or newer, because it takes advantage of the error wrapping introduced in that release. For earlier versions it points at the v1.0.0 tag, which does not rely on Go 1.13 features.
How does hashicorp/go-multierror handle errors.Is and errors.As?
The README states the package is compatible with the standard library errors package and supports As, Is and Unwrap. Note that Unwrap returns errors one at a time via chaining, unlike errors.Join, which implements the newer Unwrap() []error signature.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/hashicorp-go-multierror)