JetBrains Guide: Elegant Go Error Handling – From error Interface to Go 1.26 Generics

This JetBrains guide covers Go error handling fundamentals, wrapping with %w, type assertions using errors.Is/As/AsType, joining errors, context cancellation causes, panic/recover boundaries, and best practices for robust error management in production systems.

TonyBai
TonyBai
TonyBai
JetBrains Guide: Elegant Go Error Handling – From error Interface to Go 1.26 Generics

Prerequisites

The guide assumes familiarity with Go basics. All example code is embedded; a companion repository error-handling is available in the JetBrains Go blog code samples for hands-on practice.

Mainstream Error Handling Techniques in Go

Returning Errors

Go treats errors as values. The built-in error interface is defined as:

type error interface {
    Error() string
}

Functions return errors as the last return value. Example ReadFile demonstrates checking input, wrapping errors with fmt.Errorf("open failed: %w", err) to preserve the error chain, and using defer f.Close() after error checking to avoid nil pointer dereference.

Panic and Recover

Panic/recover is not for routine errors. It should only handle truly unexpected, unrecoverable conditions (e.g., hardcoded regex compilation failure via regexp.MustCompile). The net/http server uses recover to isolate request goroutine panics. Recovery pattern:

defer func() { if r := recover(); r != nil { /* handle */ } }()

.

Logging Errors

Use standard log or structured slog (Go 1.21+). Libraries should avoid logging internally; return errors to callers.

Error Wrapping

Errors bubble up the call stack. Wrap with fmt.Errorf("context: %w", err) to add context while preserving the original error via Unwrap(). String concatenation ( errors.New("msg" + err.Error())) flattens the error and loses type information.

Unwrapping Wrapped Errors

errors.Unwrap(err)

returns the immediate wrapped error. Repeated calls traverse the chain.

Checking Specific Error Types

The errors package provides three functions:

errors.Is()

Checks if an error matches a target sentinel error (e.g., fs.ErrNotExist) anywhere in the chain.

errors.As()

Checks for a specific error type and extracts it into a pointer variable. Requires pre-declared target pointer.

errors.AsType() (Go 1.26)

Generic, type-safe alternative: func AsType[E error](err error) (E, bool). Avoids reflection, enables compile-time checking, and scopes matched variables to the if-block. Recommended for new code.

if pathErr, ok := errors.AsType[*fs.PathError](err); ok {
    log.Println("path error at:", pathErr.Path)
} else if linkErr, ok := errors.AsType[*os.LinkError](err); ok {
    log.Println("link error during:", linkErr.Op)
}

Joining Multiple Errors

errors.Join

(Go 1.20) combines multiple errors into one. The joined error is a []error internally; errors.Unwrap returns nil. Use type assertion to access the slice: e, ok := err.(interface{ Unwrap() []error }).

Context-Based Error Handling

context.WithCancelCause

(Go 1.20) attaches a custom error to context cancellation. Call cancel(myError); retrieve with context.Cause(ctx).

Best Practices

Use defer for Cleanup

Place defer after error checks to avoid nil pointer dereference.

Provide Specific Error Context

Always wrap errors with meaningful context (function name, parameters) rather than passing raw errors.

Use Panic/Recover Only When Necessary

Reserve panic for programmer errors (hardcoded invariants) or unrecoverable system failures (OOM). Expected failures (user input, network) must be returned as errors.

Choose Libraries with Good Error Handling

Audit third-party libraries for proper error wrapping and context propagation.

Create Custom Error Types When Appropriate

Implement Error() and optionally Unwrap(), Timeout(), and structured fields (like fs.PathError with Op, Path, Err).

Handling Specific Error Types

Network Errors

Use net.OpError.Temporary() to distinguish transient failures and implement retry logic (simple loop or exponential backoff).

I/O Errors

Most I/O functions return (n int, err error). Use the byte count n to resume interrupted operations. io.EOF signals successful end-of-stream; implementers must follow the documented semantics: return (n, EOF) or (n, nil) on final read, then (0, EOF) on next call.

…when Reader encounters end-of-file after reading non-zero bytes, it may return either err == EOF or err == nil . The next Read should return 0, EOF .

Common Pitfalls to Avoid

Ignoring Errors

Never assign errors to blank identifier _. Use linters to detect unchecked errors.

Passing Errors Without Wrapping Context

Always add context when propagating errors; even the function name helps trace the call chain.

Overly Generic Error Messages

Avoid vague messages like "database error". Include specific details or use structured custom error types.

Using Inappropriate Error Types

Error types carry metadata; choose types that enable errors.Is/As/AsType checks.

Missing Error Logging

Log errors at the point they are handled or when they cannot be propagated further (e.g., in main).

Using log.Fatal()

log.Fatal

calls os.Exit, skipping all deferred cleanup. Restrict to top-level main with no pending defers.

Insufficient Error Recovery Consideration

Don't always crash. Consider fallback logic, high-availability SLAs, and isolating failures to individual goroutines (as http.ListenAndServe does).

Conclusion

Go's error handling syntax is minimal, but mastery lies in contextual decision-making and disciplined propagation. This guide covers practical techniques, modern APIs (Go 1.20/1.26), best practices, and anti-patterns to build maintainable, debuggable systems.

Original article: https://blog.jetbrains.com/go/2026/09/02/how-to-handle-errors-in-go/

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Gobest practiceserror handlingerror wrappingerrors packagepanic/recovererrors.Aserrors.Iserrors.AsTypecontext.WithCancelCausecustom error typeserrors.JoinI/O errorsnetwork errors
TonyBai
Written by

TonyBai

Tony Bai's tech world (tonybai.com). Not satisfied with just "knowing how", we strive for mastery. Focused on Go language internals, high-quality engineering practices, and cloud‑native architecture, exploring cutting‑edge intersections of Go and AI. Gophers who pursue technology are welcome—follow me and evolve with Go.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.