Modern Go Error Handling Patterns: A Complete Guide to JetBrains' go-modern-guidelines

JetBrains' go-modern-guidelines recommends replacing direct error comparisons with errors.Is and errors.As (Go 1.13+), aggregating multiple errors using errors.Join (Go 1.20+), and adopting errors.AsType[T] for generic type assertions (Go 1.26+) to produce safer, more maintainable code.

The JetBrains/go-modern-guidelines repository defines authoritative conventions for contemporary Go development, focusing on modern Go error handling patterns that leverage standard library utilities introduced in recent releases. These recommendations, documented in FEATURES.md and enforced via internal/guidelines/guidelines.json, replace fragile manual error inspection with composable, wrapping-aware APIs. By following these patterns, developers ensure their error handling remains correct even when errors traverse multiple layers of abstraction.

Use errors.Is and errors.As for Sentinel and Typed Error Inspection (Go 1.13+)

According to the guidelines defined in FEATURES.md section 5.1, Go 1.13 introduced wrapping-aware error inspection that should replace direct equality checks and type assertions. The errors.Is function traverses error chains to detect sentinel values, while errors.As safely extracts typed errors from wrapped chains.

Replace direct equality checks with errors.Is:

// Anti-pattern: Direct comparison fails on wrapped errors
if err == os.ErrNotExist {
    return nil
}

// Modern pattern: errors.Is walks the error chain
if errors.Is(err, os.ErrNotExist) {
    return nil
}

Replace manual type assertions with errors.As:

// Anti-pattern: Type assertion breaks with wrapped errors
if e, ok := err.(*os.PathError); ok {
    handle(e)
}

// Modern pattern: errors.As unwraps automatically
var pathErr *os.PathError
if errors.As(err, &pathErr) {
    handle(pathErr)
}

The errors.Is method respects custom Is methods on error types, ensuring that sentinel errors are detected even when wrapped with additional context. Similarly, errors.As provides safe type assertion across wrapped chains without manual unwrapping logic.

Aggregate Errors with errors.Join (Go 1.20+)

The errors.Join function, available since Go 1.20 and documented in FEATURES.md section 5.2, provides the standard mechanism for combining multiple errors. This approach preserves original error values for downstream inspection via errors.Is and errors.As, unlike string concatenation which destroys type information.

// Anti-pattern: Manual aggregation loses type information
var errs []error
if err := op1(); err != nil {
    errs = append(errs, err)
}
if err := op2(); err != nil {
    errs = append(errs, err)
}
if len(errs) > 0 {
    return fmt.Errorf("multiple errors: %v", errs)
}

// Modern pattern: errors.Join preserves error chain
var errs []error
if err := op1(); err != nil {
    errs = append(errs, err)
}
if err := op2(); err != nil {
    errs = append(errs, err)
}
if len(errs) > 0 {
    return errors.Join(errs...)
}

According to the specification in internal/guidelines/guidelines.json, errors.Join returns nil when the input slice is empty, eliminating the need for explicit length checks in many scenarios.

Leverage errors.AsType[T] for Generic Type Assertions (Go 1.26+)

For Go 1.26 and later, the guidelines recommend errors.AsType[T] as a type-safe alternative to the traditional two-step errors.As pattern. This generic function returns both the typed error and a boolean success indicator, eliminating the need for pre-declared target variables.

// Legacy pattern: Pre-declared variable required
var pathErr *os.PathError
if errors.As(err, &pathErr) {
    handle(pathErr)
}

// Modern pattern: errors.AsType returns value and boolean directly
if pathErr, ok := errors.AsType[*os.PathError](err); ok {
    handle(pathErr)
}

As defined in internal/guidelines/guidelines.json entry errors_as_type, this pattern removes the ceremonial pointer-to-target syntax while maintaining full compatibility with wrapped error chains.

Source Code Architecture

The error handling recommendations are enforced and tested across several key files in the repository:

Summary

  • errors.Is and errors.As (Go 1.13+): Replace direct error comparisons and type assertions with wrapping-aware inspection functions that traverse error chains.
  • errors.Join (Go 1.20+): Accumulate multiple errors using the standard library function instead of custom string formatting to preserve type information for downstream analysis.
  • errors.AsType[T] (Go 1.26+): Adopt the generic type assertion helper to eliminate temporary variables and pointer syntax when extracting typed errors from chains.

Frequently Asked Questions

When should I use errors.Is instead of direct error comparison?

Use errors.Is whenever you need to check if an error matches a specific sentinel value, especially when the error might have been wrapped with additional context using fmt.Errorf with the %w verb. Direct equality checks fail when errors are wrapped, whereas errors.Is traverses the entire error chain and respects custom Is methods defined on error types.

How does errors.Join differ from manually concatenating error strings?

errors.Join preserves the original error values as a linked chain, allowing downstream code to use errors.Is or errors.As to inspect individual components. Manual string concatenation destroys type information and creates opaque errors that cannot be programmatically inspected. Additionally, errors.Join returns nil when given an empty slice, simplifying aggregation logic.

What is the advantage of errors.AsType[T] over the traditional errors.As?

errors.AsType[T] eliminates the need to pre-declare a target variable and pass its pointer to the function. Instead, it returns the typed error value and a boolean success indicator directly, reducing boilerplate and opportunity for variable shadowing bugs. This generic approach, available in Go 1.26+, makes error type extraction more concise while maintaining the same unwrapping capabilities as errors.As.

Which Go versions support these modern error handling patterns?

According to internal/guidelines/guidelines.json, errors.Is and errors.As require Go 1.13 or later, errors.Join requires Go 1.20 or later, and errors.AsType[T] requires Go 1.26 or later. The repository's test suite in internal/guidelines/guidelines_test.go validates that these recommendations are only suggested when the target Go version meets these minimum requirements.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →