REST Error Envelope Design
Fast recall
Quick revision
Core answer
type (URI), title, status, detail, instance, plus extension fields for machine-readable context (errors for field-level validation, code for a stable enum). Spring 6/Boot 3 gives you ProblemDetail and @RestControllerAdvice handlers to translate exceptions. Stripe-style flat {error: {type, code, message, param}} is also valid — pick one and apply it universally. Always include a stable error `code` (not the HTTP status), a correlation/trace ID, and never leak stack traces or internal paths. The envelope is a contract; changing it silently breaks every client's error-handling code.Whether you can design a single error envelope that covers 4xx/5xx, validation failures, and unexpected exceptions — and whether you know RFC 7807 `ProblemDetail` as the modern baseline.
Returning different error shapes from different layers — Spring's default `{timestamp, status, error, message, path}`, your own `{success:false, error:'...'}`, and Jackson deserialization errors leaking `MethodArgumentNotValidException` verbatim. Also: using the HTTP status as the application error code, so you can't distinguish `insufficient_funds` from `invalid_card_number`.
Compare RFC 7807 `ProblemDetail` to Stripe's flat envelope and Google AIP-193; explain extension-member conventions, how to carry a stable machine-readable `code` alongside the `type` URI, when to set `type` to a dereferenceable docs URL, and how trace-id propagation makes support debugging tractable.
Interview-ready guide
Interview answer
An error envelope is a contract, not a convenience. Once a client ships, every field in your error response is something they're parsing.
The envelope I default to
- RFC 7807
application/problem+json—type,title,status,detail,instance, plus extensions - First-class in Spring 6 and Boot 3 via
ProblemDetail - The
typeURI doubles as a link to docs, which shortens support cycles
What must be in the envelope
- A stable machine-readable
code— never the HTTP status, because multiple codes share a status - A
traceId— every error I respond with correlates with APM and logs - An
instance— URI of the request for reproducibility - For validation, an
errors[]array with per-field{field, code, message} - A human
detailfor developer tooling, never for end users
What never goes in
- Stack traces, SQL strings, internal paths — security leak and noise
- PII beyond what the caller already sent
- Tokens or secrets — even in rate-limit responses
Spring implementation
- Single
@RestControllerAdvicewith@ExceptionHandlerfor domain exceptions,handleMethodArgumentNotValidfor bean validation, and a final@ExceptionHandler(Throwable.class)so nothing leaks unfiltered - Extension fields via
pd.setProperty("code", ...)andpd.setProperty("traceId", ...)
Stripe-flat as an alternative
{error: {type, code, message, param}}— perfectly valid if applied universally- The mistake is mixing RFC 7807 for some endpoints and Stripe-flat for others — clients can't write one error handler
Design mistakes I watch for
- A
success: falseflag — couples error semantics to a boolean, breaks HTTP expectations - Status code as the only differentiator — clients can't tell
VALIDATION_FAILEDfromBODY_MALFORMED - Leaking
org.hibernate.ConstraintViolationException: could not execute statement— the catch-all advice must translate - Renaming envelope fields mid-lifecycle — treat the envelope as public contract, version accordingly
- Too many fine-grained codes nobody documents — start coarse, split when codes are client-actionable
Security subtleties
- Auth errors shouldn't reveal whether a username exists — one
AUTH_INVALID_CREDENTIALSfor both wrong-user and wrong-password - Access-control errors sometimes need to be
404not403to avoid leaking resource existence
REST Error Envelope Design
Direct Answer
Ship one error shape across every endpoint. The 2026 default is RFC 7807 application/problem+json — type (URI), title, status, detail, instance, plus extension fields for machine-readable context (errors for field-level validation, code for a stable enum). Spring 6/Boot 3 gives you ProblemDetail and @RestControllerAdvice handlers to translate exceptions. Stripe-style flat {error: {type, code, message, param}} is also valid — pick one and apply it universally. Always include a stable error code (not the HTTP status), a correlation/trace ID, and never leak stack traces or internal paths. The envelope is a contract; changing it silently breaks every client's error-handling code.
Code Example
# Single error — e.g. insufficient funds
HTTP/1.1 402 Payment Required
Content-Type: application/problem+json
X-Trace-Id: 7a9c3f12b4e84e0a
{
"type": "https://errors.api.acme.com/billing/insufficient-funds",
"title": "Insufficient funds",
"status": 402,
"detail": "Your balance is $12.40; this transaction requires $50.00.",
"instance": "/v1/charges/ch_abc123",
"code": "BILLING_INSUFFICIENT_FUNDS",
"traceId": "7a9c3f12b4e84e0a",
"balance": "12.40",
"required": "50.00"
}
# Validation error — field-level details as an extension member
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
{
"type": "https://errors.api.acme.com/validation/invalid-request",
"title": "Validation failed",
"status": 400,
"detail": "2 fields failed validation.",
"instance": "/v1/customers",
"code": "VALIDATION_FAILED",
"traceId": "d3f1...",
"errors": [
{ "field": "email", "code": "NOT_EMAIL", "message": "must be a well-formed email address" },
{ "field": "birthDate", "code": "IN_PAST", "message": "must be in the past" }
]
}
# Auth failure
HTTP/1.1 401 Unauthorized
Content-Type: application/problem+json
WWW-Authenticate: Bearer realm="api", error="invalid_token"
{
"type": "https://errors.api.acme.com/auth/invalid-token",
"title": "Invalid or expired token",
"status": 401,
"detail": "Refresh your session and retry.",
"code": "AUTH_INVALID_TOKEN",
"traceId": "..."
}
Common Mistakes & Pitfalls
Returning different error shapes from different layers — Spring's default {timestamp, status, error, message, path}, your own {success:false, error:'...'}, and Jackson deserialization errors leaking MethodArgumentNotValidException verbatim. Also: using the HTTP status as the application error code, so you can't distinguish insufficient_funds from invalid_card_number.
Check your understanding
Answer these without looking back at the article.
- How do you map `MethodArgumentNotValidException` into an RFC 7807 ProblemDetail with per-field errors?
- Why should the machine-readable `code` be separate from the HTTP status, and how do you version it?
- When would you choose Stripe-style flat envelope over RFC 7807 `ProblemDetail`?
- What's the right way to avoid leaking stack traces from an unexpected exception in Spring Boot?
Related interview prep categories
1Want a guided curriculum order?
These modules are also sequenced in our flagship backend interview track — open it when you prefer an ordered path instead of browsing by topic.