As a principal, how would you standardize error handling across services using ProblemDetail, and what are the trade-offs vs a custom error envelope?
answer
- Shared ErrorResponse exception library
- Governed stable type-URI catalog
- Mandatory traceId + machine code + timestamp extensions
- problemdetails.enabled for framework errors
- envelope mismatch + 5xx masking + RFC 9457 supersedes 7807
basics
~20 sAdopt RFC 7807 ProblemDetail as the org-wide error format: stable, documented type URIs; a shared library of ErrorResponse-based exceptions; consistent extension fields (traceId, code). Trade-offs vs a custom envelope: standardization and tooling vs full control and matching a pre-existing wrapper.
solid answer
~40 sI would make application/problem+json the contract for all services and centralize it: a shared library of ErrorResponse/ErrorResponseException-based domain exceptions so errors are self-describing and i18n-ready, a governed catalog of stable type URIs pointing to docs, and a fixed set of extension members (traceId, machine-readable code, timestamp) enforced via a base ResponseEntityExceptionHandler. Enable spring.mvc.problemdetails.enabled so framework errors match. Trade-offs: RFC 7807 buys interoperability, client/tooling support, and a spec to point to, but it can clash with an existing success-response envelope (many APIs wrap everything in {data, meta}), and 'type' as a URI is more ceremony than a simple string code. For 5xx I mask internal detail and rely on traceId for correlation. RFC 9457 supersedes 7807 but is wire-compatible.
code
java · 19 lines// Shared base advice enforcing org-wide error conventions
public abstract class BaseProblemHandler extends ResponseEntityExceptionHandler {
@Override
protected ResponseEntity<Object> handleExceptionInternal(
Exception ex, Object body, HttpHeaders headers,
HttpStatusCode status, WebRequest request) {
ResponseEntity<Object> re =
super.handleExceptionInternal(ex, body, headers, status, request);
if (re != null && re.getBody() instanceof ProblemDetail pd) {
pd.setProperty("traceId", MDC.get("traceId"));
pd.setProperty("timestamp", Instant.now());
if (status.is5xxServerError()) {
pd.setDetail("An internal error occurred"); // mask internals
}
}
return re;
}
}go deeper
Not expected — this is an architecture/governance question.
Can note that a shared format and traceId help, but may miss governance and envelope trade-offs.
Covers shared exception library, extension conventions, and 5xx masking.
Weighs interop vs envelope mismatch, governs type-URI stability and machine codes, plans versioned rollout, and knows RFC 9457 supersession.
## Goal: one error contract, many services At scale the win is *predictability*: every service, in every language ideally, returns the same error shape so gateways, client SDKs, and observability tools parse errors uniformly. RFC 7807 / `application/problem+json` gives you a ready-made, documented standard instead of a bespoke shape each team reinvents. ## How I'd standardize with Spring 1. **Shared exception library.** Define base domain exceptions extending `ErrorResponseException` (implementing `org.springframework.web.ErrorResponse`). Because they carry status + headers + `ProblemDetail`, they render themselves — no per-service `@ExceptionHandler` boilerplate — and support `MessageSource` i18n via `problemDetail.<FQCN>` codes. 2. **Governed type-URI catalog.** Treat `type` URIs as API contract: a registry like `https://errors.acme.com/{domain}/{slug}` resolving to human docs. Stability matters — a changed `type` is a breaking change. Use `about:blank` only where no documentation exists. 3. **Mandatory extension members.** Enforce a common set via a shared base `ResponseEntityExceptionHandler` overriding `handleExceptionInternal`/`createProblemDetail`: `traceId` (correlation with distributed tracing), a machine-readable `code` (clients switch on this, not on human text), and `timestamp`. `detail`/`title` stay human-readable and localizable. 4. **Cover framework errors.** Set `spring.mvc.problemdetails.enabled=true` (and `spring.webflux.problemdetails.enabled=true` for reactive) so validation/method/media-type errors match the same format. 5. **Security posture.** For 5xx, never expose stack traces, SQL, or internal identifiers in `detail`; return a generic message plus `traceId` and log specifics server-side. This is both a security and a support-experience decision. ## Trade-offs vs a custom envelope **RFC 7807 pros:** a real spec to cite; growing client/tooling/OpenAPI support; free framework integration in Spring; interop across polyglot services. **RFC 7807 cons / friction:** - **Envelope mismatch.** Many orgs already wrap *success* bodies in `{ "data": …, "meta": … }`. Problem+json is a *different* top-level shape, so clients must branch on content type or status. Some teams instead fold errors into their existing envelope for consistency — losing the standard. - **`type` as URI ceremony.** A URI is heavier than a simple string error code; teams often add a `code` extension anyway and rarely dereference the URI. - **Machine vs human fields.** `title`/`detail` are for humans and may be localized/changed; clients must branch on `status` + a stable `code`, not on text — a discipline you must enforce. - **Partial adoption risk.** Flipping `problemdetails.enabled` changes the wire format of framework errors; if some services adopt and others don't, clients face two formats — worse than either alone. Roll out behind versioning. **Custom envelope pros:** full control, single unified shape for success and error, matches legacy contracts. **Cons:** no standard, every client re-learns it, no off-the-shelf tooling. ## Versioning & migration RFC 9457 obsoletes 7807 but is wire-compatible (same fields), so 'ProblemDetail' code needs no change. Migrate a live API by versioning the endpoint or content-negotiating, since changing error shape is a breaking contract change for existing clients. ## Bottom line Use ProblemDetail as the default for new/greenfield and externally-facing APIs where interop matters; consider a custom envelope only when you must match a pre-existing wrapper across success and error and are willing to forgo the standard. Enforce type-URI governance, stable machine codes, traceId correlation, and 5xx masking regardless of choice.
- Why add a machine-readable 'code' extension when RFC 7807 already has 'type'?Clients should switch on a stable, compact code rather than dereference a URI or parse human-readable text; type is a documentation link, code is the programmatic contract. It also decouples client logic from localized title/detail.
- What is the risk of enabling problemdetails on only some services?Clients then face two error formats across the org — RFC 7807 from some services and the legacy shape from others — which is harder to consume than either format used consistently; roll out uniformly or behind versioning.
- Does moving from RFC 7807 to RFC 9457 require code changes?No — 9457 obsoletes 7807 but keeps the same fields and media type, so Spring's ProblemDetail representation is unchanged.
saying these in an interview costs you the question
- Encouraging clients to branch on the human-readable title/detail text instead of status + a stable code
- Leaking internal detail in 5xx problem bodies
- Treating type URIs as free to change rather than part of the API contract
- Assuming problem+json coexists seamlessly with a {data, meta} success envelope without client-side branching