Go

What is errors.Join? How do multi-errors interact with errors.Is, errors.As and errors.Unwrap?

Question 299HardGo 1.22 to 1.25

errors.Join(errs ...error) (Go 1.20) returns an error that wraps every non-nil argument. It returns nil if all arguments are nil. Its message is the messages joined with newlines. It implements Unwrap() []error, and fmt.Errorf with several %w verbs produces the same shape.

var ErrA = errors.New("a")
var ErrB = errors.New("b")

err := errors.Join(ErrA, nil, fmt.Errorf("ctx: %w", ErrB))
fmt.Println(err)                  // a\nctx: b  (two lines)
fmt.Println(errors.Is(err, ErrB)) // true: Is/As walk every branch
fmt.Println(errors.Unwrap(err))   // <nil>  ← gotcha!

if u, ok := err.(interface{ Unwrap() []error }); ok {
	for _, e := range u.Unwrap() {
		fmt.Println("-", e)
	}
}
fmt.Println(errors.Join(nil, nil) == nil) // true

Gotcha: errors.Unwrap only calls Unwrap() error, so it returns nil for joined errors. Use type assertion to walk them manually. The traversal is depth-first and pre-order, and errors.As returns the first match. Typical uses: validation that reports every bad field, cleanup that has to report both the main error and the Close error, and fan-out work.

More on Error Handling & panics

All 37 Error Handling & panics questions