Go Error Handling Best Practices: From Basics to Advanced
The article explains why proper error handling is crucial for reliable Go programs, describes Go's explicit error return model, demonstrates basic techniques, introduces custom error types, error wrapping and chaining introduced in Go 1.13, outlines best‑practice guidelines, and reviews popular libraries and centralised patterns.
Effective error handling is essential for building reliable and maintainable software; in Go, errors are represented by the error interface and returned explicitly from functions, which makes error flow clear but requires disciplined handling.
Basic error handling
Errors are defined by implementing the Error() string method:
type error interface {
Error() string
}Functions return an error value alongside normal results:
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}Callers must check the returned error before using the result:
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Result:", result)Advanced error handling
For complex scenarios, define custom error types to carry additional context:
type AppError struct {
Code int
Message string
}
func (e *AppError) Error() string {
return fmt.Sprintf("%s (code: %d)", e.Message, e.Code)
}
func processRequest(id int) error {
if id <= 0 {
return &AppError{Code: 400, Message: "Invalid ID"}
}
// process request …
return nil
}Go 1.13 introduced error wrapping with the %w verb, allowing errors to carry a chain of context:
func readFile(filename string) error {
err := openFile(filename)
if err != nil {
return fmt.Errorf("failed to read file %s: %w", filename, err)
}
return nil
}
func openFile(filename string) error {
return errors.New("file not found")
}Wrapped errors can be inspected with errors.Is and errors.Unwrap to locate the root cause:
err := readFile("nonexistent.txt")
if err != nil {
fmt.Println("Error:", err)
var fileErr error = errors.New("file not found")
if errors.Is(err, fileErr) {
fmt.Println("This is a file not found error")
}
}Best‑practice guidelines
Never ignore errors; always handle or propagate them.
Return errors as soon as they are detected to avoid unnecessary work.
Provide clear, specific error messages that include relevant data.
Use fmt.Errorf with %w to add context while preserving the original error.
Distinguish between recoverable and unrecoverable errors—log and continue for the former, terminate or abort for the latter.
Error‑handling libraries
pkg/errors offers stack traces and richer wrapping:
import "github.com/pkg/errors"
func readFile(filename string) error {
return errors.New("file not found")
}
func process() error {
err := readFile("config.json")
if err != nil {
return errors.Wrap(err, "process failed")
}
return nil
}
func main() {
if err := process(); err != nil {
fmt.Println("Error:", err)
fmt.Println("Stack trace:", errors.WithStack(err))
}
}xerrors (standardised in Go 1.13) provides similar wrapping capabilities:
import "golang.org/x/xerrors"
func readFile(filename string) error {
return xerrors.New("file not found")
}
func process() error {
err := readFile("config.json")
if err != nil {
return xerrors.Errorf("process failed: %w", err)
}
return nil
}
func main() {
if err := process(); err != nil {
fmt.Println("Error:", err)
fmt.Println("Root cause:", xerrors.Unwrap(err))
}
}Error‑handling patterns
A centralised error handler can map error types to HTTP status codes:
func handleError(w http.ResponseWriter, err error) {
switch e := err.(type) {
case *AppError:
http.Error(w, e.Message, e.Code)
default:
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}
func handler(w http.ResponseWriter, r *http.Request) {
if err := processRequest(r); err != nil {
handleError(w, err)
return
}
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "Success")
}Logging errors with context aids debugging:
func process() error {
// processing …
return errors.New("process failed")
}
func main() {
if err := process(); err != nil {
log.Printf("Error: %v", err)
}
}Conclusion
Go’s explicit error model encourages clear, controllable error handling. By mastering basic patterns, custom error types, the Go 1.13 wrapping features, and using libraries such as pkg/errors or xerrors , developers can write Go code that is both robust and easy to maintain.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Golang Shines
We share daily the latest Golang technical articles, practical resources, language news, tutorials, and real-world projects to help everyone learn and improve.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
