# Understanding errors.Join for Aggregating Errors in Go (Go 1.20+)

> Discover how errors Join aggregates multiple errors in Go 1.20+ preserving individual error inspection with errors Is and errors As for robust error handling.

- Repository: [JetBrains/go-modern-guidelines](https://github.com/jetbrains/go-modern-guidelines)
- Tags: deep-dive
- Published: 2026-08-29

---

**`errors.Join` provides a standard library mechanism for combining multiple error values into a single error while preserving the ability to inspect individual failures using `errors.Is` and `errors.As`.**

Introduced in Go 1.20, `errors.Join` represents the modern approach to handling multiple concurrent failures without resorting to fragile string concatenation. According to the JetBrains/go-modern-guidelines repository, this function treats aggregated errors as a collection rather than a merged string, ensuring that non-nil errors remain distinguishable through Go's standard error-matching interfaces. This capability makes it indispensable for validation pipelines, concurrent operations, and any function that must report multiple distinct failures.

## The Purpose and Behavior of errors.Join

The `errors.Join` function serves a specific need in Go error handling: it aggregates multiple errors into a single return value while maintaining the granularity required for proper error inspection. As documented in the repository's [`internal/guidelines/guidelines.json`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines.json) under the **"errors_join"** entry, this function returns `nil` only when all provided errors are `nil`, and otherwise returns a non-nil error that wraps each constituent error.

Unlike manual concatenation approaches, `errors.Join` creates an error tree that preserves the original error types and values. This design enables downstream code to use `errors.Is` to check for specific sentinel errors and `errors.As` to extract custom error types, regardless of how many errors were aggregated upstream.

## Comparing errors.Join to Traditional Approaches

Before Go 1.20, developers typically aggregated errors using `fmt.Errorf` with format verbs, which either lost error identity or produced brittle string matching:

```go
// Pre-Go 1.20 approach: loses granular error checking
if err1 != nil && err2 != nil {
    return fmt.Errorf("%v; %w", err1, err2)
}

```

The `errors.Join` approach eliminates these limitations by treating each error as a distinct node in the error chain. As noted in the repository's [`FEATURES.md`](https://github.com/JetBrains/go-modern-guidelines/blob/main/FEATURES.md) file, this implementation provides first-class support for error aggregation that works seamlessly with Go's existing error inspection tools.

## Practical Implementation Patterns

### Basic Error Aggregation

The simplest use case involves combining two or more errors directly:

```go
return errors.Join(err1, err2)

```

This syntax replaces verbose conditional blocks and ensures that both errors remain accessible to callers via `errors.Is` and `errors.As`.

### Aggregating Dynamic Error Collections

When collecting errors from multiple operations in a loop or conditional chain, accumulate them in a slice and expand it using variadic syntax:

```go
var errs []error
if err := doSomething(); err != nil {
    errs = append(errs, err)
}
if err := doAnotherThing(); err != nil {
    errs = append(errs, err)
}
return errors.Join(errs...)

```

This pattern, highlighted in the [`internal/guidelines/guidelines.go`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines.go) parsing logic, is particularly effective for validation functions that must report all constraint violations, not just the first failure encountered.

### Inspecting Individual Errors in Joined Results

Once errors are joined, callers can still inspect specific error types using standard library functions:

```go
if err := someFunc(); err != nil {
    if errors.Is(err, io.EOF) {
        // handle EOF specifically
    }
    if errors.As(err, &myCustomErr) {
        // handle custom error type
    }
}

```

The joined error implements the `Unwrap() []error` interface method, allowing `errors.Is` and `errors.As` to traverse the entire collection of aggregated errors.

## Source Code References and Guidelines

The JetBrains/go-modern-guidelines repository provides authoritative documentation for `errors.Join` usage across three key locations:

- **[`internal/guidelines/guidelines.json`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines.json)** – Contains the **"errors_join"** guideline entry that defines the standard practice for aggregating errors since Go 1.20
- **[`FEATURES.md`](https://github.com/JetBrains/go-modern-guidelines/blob/main/FEATURES.md)** – Tracks the implementation status and availability of the `errors.Join` feature within the guidelines ecosystem
- **[`internal/guidelines/guidelines.go`](https://github.com/JetBrains/go-modern-guidelines/blob/main/internal/guidelines/guidelines.go)** – Implements the parser that presents the `errors_join` guideline data to tooling and documentation generators

These resources collectively establish `errors.Join` as the recommended replacement for manual error concatenation in modern Go codebases.

## Summary

- **`errors.Join`** combines multiple errors into a single return value without losing individual error identity
- The function returns `nil` only when all input errors are `nil`, simplifying error checking logic
- Aggregated errors remain fully compatible with **`errors.Is`** and **`errors.As`** for granular error inspection
- The JetBrains/go-modern-guidelines repository documents this pattern in the **"errors_join"** guideline entry for Go 1.20+
- This approach eliminates the need for manual string formatting using `fmt.Errorf` when collecting multiple failures

## Frequently Asked Questions

### When should I use errors.Join instead of fmt.Errorf?

Use `errors.Join` when you need to combine multiple independent errors that should remain inspectable as distinct entities, such as collecting validation failures or concurrent operation errors. Reserve `fmt.Errorf` with the `%w` verb for wrapping a single error with additional context, as it does not support aggregating multiple errors while preserving their individual identities.

### How do I check if a specific error is present in a joined error?

Use the standard `errors.Is` or `errors.As` functions exactly as you would with a single wrapped error. Because `errors.Join` implements the `Unwrap() []error` interface, these functions automatically traverse the collection of aggregated errors and return true if any single error in the collection matches your check.

### What happens if all errors passed to errors.Join are nil?

If all arguments are `nil`, `errors.Join` returns `nil`. This behavior allows you to safely aggregate errors from optional operations without additional nil checks, as a common pattern of appending errors to a slice and joining them at the end will naturally produce a nil result when no errors occurred.

### Is errors.Join available in all Go versions?

No, `errors.Join` was introduced in **Go 1.20**. If you must support older Go versions, you will need to implement custom error aggregation using a slice-based error type that implements the `Error()` string method and `Unwrap() []error` interface, though this approach lacks the standard library optimizations and universal recognition of the built-in `errors.Join` implementation.