The stdlib "errors" package in Go supports joining multiple errors in the addition to more commin %w wrapping.
Joining errors
You can join multiple errors in two ways. They have slightly different semantics under the hood (look at the Appendix section if you care), but they both work in a similar way.
1. By using multiple %w verbs on the same error
Go
var (
ErrRelayOrientation = errors.New("bad relay orientation")
ErrCosmicRayBitflip = errors.New("cosmic ray bitflip")
ErrStickyPlunger = errors.New("sticky sensor plunger")
)
err1 := fmt.Errorf("G-switch failed: %w %w %w", ErrRelayOrientation, ErrCosmicRayBitflip, ErrStickyPlunger)
// 2009/11/10 23:00:00 G-switch failed: bad relay orientation cosmic ray bitflip sticky sensor plunger
log.Fatal(err1)
2. The second one uses the errors.Join function introduced in Go 1.20.
The function takes in a variadic error argument, discards any nil values, and joins the rest of the provided errors. The message is formatted by joining the strings obtained by calling each argument’s Error() method, separated by a newline.
Go
err2 := errors.Join(
ErrRelayOrientation,
ErrCosmicRayBitflip,
ErrStickyPlunger,
)
// 2009/11/10 23:00:00 bad relay orientation
// cosmic ray bitflip
// sticky sensor plunger
log.Fatal(err2)
How to use them?
By now, we’ve seen the two ways that Go supports error wrapping, direct wrapping and joined errors.
Both variants ultimately form a tree of errors. The most common ways to inspect that tree are the errors.Is and errors.As functions. Both of these examine the tree in a pre-order, depth-first traversal by successively unwrapping every node found.
Go
func Is(err, target error) bool
func As(err error, target any) bool
The errors.Is function examines the input error’s tree, looking for a leaf that matches the target argument and reports if it finds a match. In our case, this can look for a leaf node that matches a specific joined error.
Go
ok := errors.Is(err1, ErrStickyPlunger)
fmt.Println(ok) // true
On the other hand, errors.As examines the input error’s tree, looking for a leaf that can be assigned to the type of the target argument. Think of it as an analog to json.Unmarshal.
Go
var engineErr *EngineError
ok = errors.As(err2, &engineErr)
fmt.Println(ok) // false
So, to summarize:
- errors.Is checks if a specific error is part of the error tree
- errors.As checks if the error tree contains an error that can be assigned to a target type
The catch
So far so good! We can use these two types of error wrapping to form a tree. But let’s say we wanted to inspect that tree in a more manual way on another part of the codebase. The errors.Unwrap function allows you to get direct wrapped errors.
But there’s a slight complication here. Let’s try to call errors.Unwrap() directly on any of the two joined errors created above.
Go
fmt.Println(errors.Unwrap(err1)) // nil
fmt.Println(errors.Unwrap(err2)) // nil
So, why nil?! What’s going on? How can I get the original errors slice and inspect it? Turns out, that the two ‘varieties’ implement a different Unwrap method.
Go
Unwrap() error
Unwrap() []error
The documentation of errors.Unwrap method clearly states that it only calls the first one and does not unwrap errors returned by Join. There have been multiple discussions on golang/go about allowing a more straightforward way to unwrap joined errors, but there has been no consensus.
The way to achieve it right now is to either use errors.As or an inline interface cast to get access to the second Unwrap implementation.
Go
var joinedErrors interface{ Unwrap() []error }
// You can use errors.As to make sure that the alternate Unwrap() implementation is available
if errors.As(err1, &joinedErrors) {
for _, e := range joinedErrors.Unwrap() {
fmt.Println("-", e)
}
}
// Or do it more directly with an inline cast
if uw, ok := err2.(interface{ Unwrap() []error }); ok {
for _, e := range uw.Unwrap() {
fmt.Println("~", e)
}
}
So, it’s an extra little step, but with either of these techniques you’ll be able to retrieve the original slice of errors and manually traverse the error tree.
Similar Reads
Go Error Handling
In Go, error handling is done by returning error values instead of using try-catch like in Java or Python. This approach ensures explicit error handling, improving code clarity and control. The error type in Go is an interface with a single method: type error interface { Error() string}Any type impl
5 min read
Inheritance in GoLang
Inheritance means inheriting the properties of the superclass into the base class and is one of the most important concepts in Object-Oriented Programming. Since Golang does not support classes, so inheritance takes place through struct embedding. We cannot directly extend structs but rather use a c
3 min read
strings.Join() Function in Golang With Examples
strings.Join() Function in Golang concatenates all the elements present in the slice of string into a single string. This function is available in the string package. Syntax: func Join(s []string, sep string) string Here, s is the string from which we can concatenate elements and sep is the separato
1 min read
errors.New() Function in Golang with Examples
Errors package in Golang is used to implement the functions to manipulate the errors. errors.New()function returns an error that formats like given text. Each call to New returns distinct error value indeed in the event that the content is indistinguishable. Syntax: func New(text string) error It re
1 min read
Overview of Benchmark Testing in Golang
In automation testing, most of the frameworks support only one among functionality testing and benchmark testing. But the Golang testing package provides many functionalities for a different type of testing including benchmark testing. B is a type(struct) passed to Benchmark functions to manage benc
2 min read
ring package in Golang
Go language provides a ring package that implements operations on circular lists. To access the functions of the ring package you need to import the ring package in your program with the help of the import keyword. .ring-golang-table { border-collapse: collapse; width: 100%; } .ring-golang-table td
2 min read
Parsing Time in Golang
Parsing time is to convert our time to Golang time object so that we can extract information such as date, month, etc from it easily. We can parse any time by using time.Parse function which takes our time string and format in which our string is written as input and if there is no error in our form
2 min read
bits Package in Golang
Go language provides inbuilt support for bit counting and manipulation functions for the predeclared unsigned integer types with the help of the bits package. .bits-package-Golang-table { border-collapse: collapse; width: 100%; } .bits-package-Golang-table td { border: 1px solid #5fb962; text-align:
6 min read
Time Formatting in Golang
Golang supports time formatting and parsing via pattern-based layouts. To format time, we use the Format() method which formats a time.Time object. Syntax: func (t Time) Format(layout string) string We can either provide custom format or predefined date and timestamp format constants are also availa
2 min read
Top 10 Golang Frameworks in 2025
Golang (or Go) is an open-source compiled programming language that is used to build simple, systematic, and secure software. It was designed by Google in the year 2007 and has been readily adopted by developers all over the world due to its features like memory safety, structural typing, garbage co
9 min read