Programming and Software Development

A Practical Guide to Error Handling in Go: From Returning and Wrapping to Recovery

The JetBrains guide explains Go’s philosophy of handling errors as values returned within the normal execution path, and reviews techniques for returning, wrapping, inspecting, joining, and recovering from errors. It also specifies when panic and recover should be used, and how to avoid ignoring errors or losing their context as they pass between functions.

2026-09-02
6 min read
8 views
فريق تحرير certi.news
A Practical Guide to Error Handling in Go: From Returning and Wrapping to Recovery

Go relies on a somewhat different view of errors than languages such as Java, C++, JavaScript, and Python: in Go, an error is a value of the built-in error type, and a function usually returns it to the caller alongside its other return values. This makes checking and handling errors an explicit part of the program’s flow, rather than moving them into a separate exception mechanism.

The JetBrains guide, originally written by community contributor Christoph Berger and then moved to the JetBrains Go blog, presents a range of techniques and practices for handling input/output, network, data-validation, and other errors. It was updated in August 2026 to reflect the latest changes to the Go language.

Returning Is the Starting Point

When a function does not have enough context to handle a problem, it should return the error to the calling function. Go generally places the error value at the end of the return list; a function such as ReadFile() returns the file contents and an error whose value is nil on success or non-nil on failure.

The guide explains that the caller should test the error immediately rather than ignore it. If the function has opened a resource such as a file or network connection, it should use defer to clean it up on exit, but only after confirming that the opening operation succeeded, because attempting to close an invalid resource can cause an additional problem.

Adding Context Without Damaging the Error Structure

An error may pass through a chain of functions before it is handled or logged. During this passage, each function can add useful information, such as the operation that failed or the path it was processing. However, converting the error to text by concatenating err.Error() loses the error chain and its type structure.

The correct approach is to use fmt.Errorf() with the %w verb to wrap the error while retaining the original error. This later makes it possible to use errors.Unwrap() to access one layer, or errors.Is() and errors.As() to test for specific errors within the wrapping chain. Go 1.26 adds the generic errors.AsType() function as a type-safe alternative to As(); it returns the matching error and a Boolean value, allowing the compiler to detect type errors that might occur at runtime with the older approach.

Multiple Errors and Canceled Contexts

Errors are not always a linear chain. When processing a group of files, for example, some operations may succeed while others fail. The standard library provides errors.Join(), available since Go 1.20, to collect multiple errors into a single value while retaining the content that was processed successfully.

The guide notes that errors.Unwrap() returns a single value and therefore returns nil when dealing with a joined error. To access the joined errors, you must check that the value implements the interface that provides Unwrap() []error. Since Go 1.20, you can also use context.WithCancelCause() to associate a context cancellation with a custom cause, then retrieve that cause through context.Cause(ctx) instead of relying only on the general context.Canceled value.

When Is panic Appropriate?

panic and recover should not ordinarily replace error checking. Expected errors, such as invalid user input, missing files, or network timeouts, should be handled and returned through the usual return path.

Panic becomes appropriate when the problem is unexpected and there is no meaningful way to handle it, such as a failure to compile a fixed regular expression whose validity should have been checked in advance. In HTTP server cases, an application may use recover() inside a deferred function to prevent the collapse of the current request handler from affecting other requests, where possible. Situations such as running out of memory may leave the application with no practical option to continue.

What Matters Practically for Developers?

  • Do not ignore errors: Assigning the error value to the blank identifier or discarding the only return value can delay the discovery of a problem and make its later effects harder to diagnose.
  • Log in the right place: A function should handle the error or return it to the caller. The guide recommends that libraries avoid logging on their own, because their users may prefer different logs or output destinations.
  • Use appropriate types: Custom error types can carry additional information, as fs.PathError does by providing the operation, path, and internal error.
  • Distinguish network errors: net.OpError makes it possible to inspect the nature of a connection failure, and a temporary error may allow a retry or the use of a strategy such as exponential backoff.
  • Make use of the byte count in input/output operations: The io.Reader interface returns the number of bytes processed along with the error, which may help resume the operation rather than retransmitting all the data.
  • Pay attention to io.EOF: This value indicates the successful end of a read stream according to the package semantics, and is not necessarily a failure that needs to be logged as an ordinary error.
  • Do not use log.Fatal() carelessly: It calls os.Exit(), which bypasses deferred functions. For that reason, the guide suggests limiting its use or using os.Exit() in the main() function.

The editorial conclusion from certi.news is that clarity in the error path is part of program design, not merely a verbose writing style. Organized wrapping, appropriate types, and responsible logging make diagnosis actionable, while relying on panic, generic messages, or discarded errors hides the information developers will need later. The practical trade-off remains tied to the application’s context: a request server may need limited recovery, whereas an unrecoverable error may call for termination and restart.

News source
JetBrains Blog
Open original source ↗
ف
Author

فريق تحرير certi.news

In the same category

You may also like

View all news