Error Handling Patterns
Reference for both developers writing code and agents implementing features. Covers the full lifecycle: classification → representation → propagation → retry → observability.Agent Trigger
Apply when: Writing try/catch blocks, defining error types, retry logic, or reviewing error paths. Rule of thumb: Fail fast, type your errors, never silently swallow; use Result types or exceptions consistently; backoff with jitter on retries.Error Taxonomy
Understanding what kind of error you have determines how you handle it.Expected vs. Unexpected
Expected errors belong in the return type (Result/Either or checked value). Unexpected errors surface as exceptions or panics and propagate to a global handler.
Recoverable vs. Fatal
- Recoverable: caller can retry, fallback, or inform the user and continue
- Fatal: system cannot safely continue; crash or escalate immediately
Business Errors vs. Technical Errors
- Business errors: domain rule violations — surface to users with actionable messages (“Insufficient balance”)
- Technical errors: infrastructure or code failures — log internally, surface generic message to users
Fail-Fast Principle
Validate at the earliest possible point. Every validation deferred is a debugging cost multiplied. Rules:- Validate arguments before entering async or side-effecting logic
- Raise errors immediately upon detection — not after partially completing work
- In task-returning methods (async), throw argument exceptions synchronously before the async portion begins
Exception Best Practices
When to Use Exceptions
Use exceptions for truly exceptional conditions — events that are rare, unexpected, or represent violations of invariants. For conditions that occur routinely as part of normal flow, use conditional checks or Result types instead.Exception Hierarchy Design
- Derive custom exceptions from the appropriate base class, not always from the root
Exception - Name exception classes with the
Exceptionsuffix - Include three constructors:
(),(message),(message, innerException) - Add structured properties only when callers need them programmatically (not just for messages)
- Prefer predefined exception types over custom ones where they fit precisely
What to Include in Exception Messages
- Root cause, not a generic label (“Server error” is insufficient)
- What went wrong, where, and ideally what the caller can do
- Proper grammar and ending punctuation
- No PII, no internal paths, no stack trace content in the message string itself
Never Swallow Silently
Railway-Oriented Programming / Result Types
TheResult<Ok, Err> pattern (Rust, Haskell Either, Kotlin Arrow, TypeScript fp-ts) makes error handling explicit and type-safe. The compiler enforces handling — unlike exceptions, which are invisible in signatures.
When to Use Result vs. Exceptions
Error Propagation
Let It Propagate
Default stance: if you cannot handle the error meaningfully at the current layer, let it propagate. Do not catch just to re-throw the same exception without adding context.Catch-and-Rethrow: Add Context
When catching, wrap with additional context about what operation failed. Preserve the original ascause / innerException.
Preserve Stack Traces
In C#, use barethrow (not throw e) inside a catch block to preserve the original stack trace. When rethrowing outside the catch block, use ExceptionDispatchInfo.Capture / .Throw().
Rollback on Partial Mutation
If a method performs multiple side effects and one fails, undo completed steps. Callers must be able to assume no side effects when an exception escapes.Retry and Backoff
What Errors Warrant Retry
Retry only transient errors: network timeouts, rate limits, temporary unavailability. Do not retry:- Validation errors (will always fail)
- Authentication failures (credentials wrong, not transient)
- Business rule violations
Exponential Backoff with Jitter
Retry Budget
Cap total attempts and total elapsed time. See Error Budget (Agentic) for the agentic adaptation of this pattern (retry/token/runtime/session budgets). For self-healing loops: Self-Healing Loop covers the failure→retry→rollback→escalation sequence.Error Response Design (APIs)
See API Design Patterns for full REST conventions. Summary for error responses:Structured Error Body
HTTP Status + Error Code
HTTP status gives coarse classification. A machine-readablecode string provides specific identity.
Never return 200 with an error body — clients cannot distinguish success from failure without reading the body.
User-Facing vs. Developer Messages
message: safe to display to end users — no internals, no pathsdetails/debug: developer context, strip in production or gate behind auth- Never expose stack traces, SQL, or internal service names in API responses
Logging Discipline
What to Log
- Log at ERROR: unhandled exceptions, data loss scenarios, external service failures
- Log at WARN: degraded operation, retries, approaching limits, unexpected-but-handled states
- Log at INFO: significant lifecycle events (server start, job complete, user auth)
- Log at DEBUG: request details, intermediate state — only in development
Avoid Logging PII
Never log passwords, tokens, email addresses, SSNs, payment data. Mask or omit before logging.Log Error Codes
Assign numeric or string codes to known error categories. Document them. Codes allow support teams to grep logs and correlate errors without reading prose.Log Once at the Boundary
Log at the point where the error is handled or escalated — not at every layer it passes through. Multiple log entries for the same error create noise and inflate storage.Anti-Patterns
Cross-References
- API Design Patterns — error response shape, HTTP status conventions
- Software Design Principles — fail-fast and single-responsibility apply to error handling design
- Self-Healing Loop — failure→retry→rollback→escalation harness pattern
- Error Budget (Agentic) — retry/token/runtime/session budgets for agentic loops