Implementing Error Handling

1. Defining Error Response Format

StandardDetail
RFC 9457 (Problem Details)Successor to RFC 7807; application/problem+json
Fieldstype, title, status, detail, instance + extensions
Stable codeMachine-readable extension (code)

Example: Problem Details body

{
  "type": "https://api.shop.com/errors/insufficient-funds",
  "title": "Insufficient funds",
  "status": 402,
  "detail": "Account balance is below required amount",
  "instance": "/payments/pay_123",
  "code": "INSUFFICIENT_FUNDS",
  "balance": 12.50,
  "required": 49.95
}

2. Using HTTP Status Codes

RangeUse
4xxClient error; do not retry blindly
5xxServer error; safe-to-retry if idempotent
409State conflict; reconcile before retry
429Throttle; honor Retry-After
503 + Retry-AfterBackoff window

3. Implementing Error Codes

PracticeDetail
Stable stringsINSUFFICIENT_FUNDS, ORDER_NOT_FOUND
Namespacedpayments.insufficient_funds
CatalogDocumented enumeration
AvoidNumeric only; localized strings

4. Adding Error Context

FieldPurpose
requestId / traceIdCorrelate with logs
timestampUTC ISO-8601
field errorsPer-field validation messages
help URLLink to docs
AvoidStack traces in prod responses

5. Handling Validation Errors

ElementDetail
Status400 (syntax) / 422 (semantics)
Errors arrayPer-field code, message, pointer
JSON PointerRFC 6901 path: /items/0/qty
i18nCode = key; localize in client

6. Implementing Global Error Handlers

Example: Spring Boot 3 @RestControllerAdvice

@RestControllerAdvice
class GlobalErrors {
  @ExceptionHandler(EntityNotFoundException.class)
  ProblemDetail notFound(EntityNotFoundException ex) {
    var p = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
    p.setProperty("code", "NOT_FOUND");
    return p;
  }
  @ExceptionHandler(MethodArgumentNotValidException.class)
  ProblemDetail validation(MethodArgumentNotValidException ex) {
    var p = ProblemDetail.forStatus(HttpStatus.BAD_REQUEST);
    p.setProperty("errors", ex.getFieldErrors().stream()
        .map(f -> Map.of("field", f.getField(), "code", f.getCode())).toList());
    return p;
  }
}

7. Logging Errors

PracticeDetail
StructuredJSON with stable keys
IncludetraceId, userId, errorCode, exception class
Stack traceServer-side only
PIIMask before logging
LevelWARN (expected) / ERROR (unexpected)

8. Propagating Errors

ConcernPractice
TranslateWrap downstream errors with own code
Don't leak internalsStrip stack/SQL from public response
Preserve causeLog original; expose generic to client
gRPC ↔ HTTPMap status codes consistently

9. Implementing Fallback Responses

TypeExample
CachedLast successful payload
Static defaultEmpty list, defaults
Degraded dataSubset of fields
IndicatorFlag in response ("stale": true)

10. Handling Timeout Errors

ActionDetail
504 Gateway TimeoutUpstream deadline exceeded
Cancel workPropagate cancellation token
Retry?Only if idempotent; with backoff
AlertSpike in 504s = capacity issue

11. Implementing Retry Logic

SettingDefault
Max attempts3
Initial delay100–250 ms
Multiplier2.0
Jitter±50%
Retry onNetwork, 5xx (idempotent), 429 + Retry-After

12. Using Error Aggregation

ToolUse
Sentry / Bugsnag / RollbarGroup similar errors, alerting
HoneycombHigh-cardinality query of errors
OpenTelemetryErrors as span events
Slack/PagerDutyAlert routing