Error Handling Patterns

Robust error handling is the difference between an app that degrades gracefully and one that leaks stack traces or dies silently. Good handling improves reliability, makes incidents debuggable, and protects users from confusing failures.

The core idea: decide what's expected (and handle it) versus what's a bug (and fail loudly), then make every failure produce a consistent response and a useful log entry.

TL;DR

Quick Example

A central handler that logs the detail and returns a safe, consistent response:

Core Concepts

Classify your errors

The key split is operational vs programmer: operational errors are part of normal life and deserve handling; programmer errors are bugs you shouldn't paper over.

Design the error response

Use one schema everywhere, map errors to the right HTTP status, include a stable machine-readable code, and attach a request_id so logs and support tickets line up. See REST API Design.

Best Practices

Handle centrally, throw locally

Let code throw typed errors close to the problem, and convert them to responses in one middleware/handler. This keeps individual handlers clean and the output consistent.

Log with context, respond safely

Retry only what's safe

Retry idempotent, transient failures (network blips, 503s) with exponential backoff and jitter. Never blindly retry non-idempotent writes — see Message Queues for at-least-once patterns.

Common Mistakes

Swallowing errors

Leaking internals to the client

FAQ

Exceptions or result types?

Both work. Exceptions suit "exceptional" failures and centralized handling; result types (Result<T, E>) make expected errors explicit at the call site with no hidden control flow. Many codebases throw for programmer errors and return results for expected ones.

What should I return to the client?

A consistent envelope with a stable code, a human-readable message (safe for 4xx, generic for 5xx), and a request_id. Never raw stack traces or database errors.

Which errors should I retry?

Only idempotent, transient ones — timeouts, connection resets, 429/503. Use exponential backoff with jitter, and cap the attempts. Retrying a non-idempotent write can double-charge or duplicate data.

Where should I handle errors?

Throw near the cause with enough context, and convert to responses in a single central handler. Avoid scattering try/catch that each format errors differently.

Related Topics

References