#Joining Errors With errors.Join in Go

Elliot Forbes Elliot Forbes · Jan 2, 2024 · 3 min read

In Go 1.20, a powerful new feature was introduced: errors.Join. This allows you to combine multiple errors into a single error value, making it easier to handle and report on multiple failures that occur during the execution of your code.

This is super handy if you want to enrich the errors that may be getting returned from parts of your Go application and allows you to do better error checking further down the chain.

When this was announced, it was a fantastic addition to the Go programming language that simplifies error handling patterns significantly.

Understanding errors.Join

The errors.Join function takes multiple errors and returns a single error that represents all of them. This is particularly useful when you need to collect multiple failures from operations and report them together.

Here’s the basic syntax:

func Join(errs ...error) error

It accepts any number of error values and returns a single error that contains all of them.

Basic Example

Let’s start with a simple example where we perform multiple operations that might fail:

package main

import (
    "errors"
    "fmt"
)

func performOperations() error {
    var err1 error = errors.New("operation 1 failed")
    var err2 error = errors.New("operation 2 failed")
    var err3 error = nil

    // Join all the errors together
    return errors.Join(err1, err2, err3)
}

func main() {
    err := performOperations()
    if err != nil {
        fmt.Println("Errors occurred:")
        fmt.Println(err)
    }
}

When you run this, the output will display all the errors that were joined together:

Errors occurred:
operation 1 failed
operation 2 failed

Practical Use Case: Multiple Cleanup Operations

A common scenario where errors.Join is useful is when you need to perform multiple cleanup operations and want to collect any errors that occur:

package main

import (
    "errors"
    "fmt"
)

func closeResource(name string) error {
    // Simulate closing a resource
    if name == "database" {
        return errors.New("failed to close database connection")
    }
    return nil
}

func cleanup() error {
    var errs []error

    // Close multiple resources
    if err := closeResource("database"); err != nil {
        errs = append(errs, err)
    }
    if err := closeResource("cache"); err != nil {
        errs = append(errs, err)
    }
    if err := closeResource("file"); err != nil {
        errs = append(errs, err)
    }

    // Join all errors into one
    return errors.Join(errs...)
}

func main() {
    if err := cleanup(); err != nil {
        fmt.Println("Cleanup failed:")
        fmt.Println(err)
    }
}

Checking for Specific Errors

You can use errors.Is() to check if a specific error is contained within a joined error:

package main

import (
    "errors"
    "fmt"
)

var ErrDatabase = errors.New("database error")
var ErrNetwork = errors.New("network error")

func main() {
    err := errors.Join(
        ErrDatabase,
        ErrNetwork,
        errors.New("unknown error"),
    )

    // Check if specific errors are in the joined error
    if errors.Is(err, ErrDatabase) {
        fmt.Println("Database error detected")
    }

    if errors.Is(err, ErrNetwork) {
        fmt.Println("Network error detected")
    }
}

Iterating Over Joined Errors

While errors.Join combines errors, you can’t directly iterate over them. However, you can use errors.Unwrap() or type assertion to work with the underlying error structure if needed for advanced use cases.

Conclusion

The errors.Join function is a powerful addition to Go’s error handling toolkit. It simplifies scenarios where you need to collect and report multiple errors without losing information about any of them. Whether you’re performing cleanup operations, validating multiple conditions, or handling concurrent operations, errors.Join provides a clean and idiomatic way to combine errors.

By using this feature effectively, you can write more robust Go applications with better error handling and reporting.

Further Reading

If you enjoyed this, you may like my other articles on error handling in Go: