RFC 9457 Problem Details gives HTTP APIs a common container for machine-readable errors: application/problem+json. It does not create a universal catalog of business errors, replace HTTP status semantics, or authorize exposing stack traces. The standard succeeds when clients can classify a stable problem type, decide whether and how to remediate it, correlate one occurrence, and still rely on the actual HTTP response status. It fails when teams merely wrap every legacy message in five familiar fields.
The RFC 9457 specification defines type, status, title, detail, and instance, permits extension members, and obsoletes RFC 7807. Build an implementation around a small governed problem taxonomy and explicit client behavior. Keep transport status, domain classification, human explanation, and operational diagnostics separate. That separation allows a public error to be useful without becoming a debugging channel or a second, contradictory status protocol.
Design the six-stage problem-details lifecycle
Begin with failures clients must distinguish, not exception classes in server code. Group occurrences by remediation: correct a field, authenticate, request permission, wait and retry, resolve a state conflict, use a replacement resource, or contact support. Reuse an HTTP status alone when it expresses all needed semantics. Mint a problem type only when stable additional semantics improve automated or human recovery. This keeps the registry small enough for client teams to understand and test.
For each type, assign an owner and document the type URI, short title, usual HTTP status, applicability, extension schema, retry expectations, security classification, example, and lifecycle state. A type URI should remain stable and, where practical, resolve to human-readable documentation. Do not encode release numbers or transient implementation names in it. The type describes a class of problems; instance identifies a particular occurrence.
| Member | Purpose | Good practice | Common mistake |
|---|---|---|---|
type | Stable problem classification | Use a controlled URI and document remediation | Put an exception class or changing message in it |
status | Advisory copy of HTTP status | Match the actual response status | Use it to contradict a gateway response |
title | Short summary of the type | Keep stable except localization | Insert occurrence-specific values |
detail | Human-readable occurrence explanation | Explain correction without sensitive internals | Require clients to parse it |
instance | Identifier for this occurrence | Use an opaque, safe correlation URI or identifier | Expose internal hostnames or database keys |
| Extensions | Typed machine-actionable context | Define names, types, absence rules and sensitivity | Add arbitrary debug data per service |
Make HTTP status and headers authoritative
Choose status codes according to HTTP semantics before choosing a problem type. Authentication failure, authorization refusal, absent resource, invalid representation, state conflict, rate limit, and temporary service failure have different implications for caches, intermediaries, monitoring, and generic clients. The status member is advisory; generators must send the same value in the actual response. A client should use the HTTP response status for protocol behavior and the problem type for application-specific handling.
Use established headers where they carry protocol information. Retry-After can communicate an appropriate retry time; authentication challenges belong in WWW-Authenticate; content negotiation still uses Content-Type and Accept. Do not duplicate every header in extension members. If a gateway rewrites the status, investigate the boundary rather than teaching clients that body and response status routinely disagree.
Define extensions as durable client contracts
Extensions should answer machine-actionable questions that the base members cannot. Validation failures might include a list of locations, rule identifiers, and safe messages. A quota problem might identify a quota class and reset policy. A state conflict might include the current version or permissible transition. Specify each extension's JSON type, requiredness, cardinality, ordering significance, localization behavior, and compatibility rules. Clients must tolerate unrecognized extensions so producers can add nonbreaking context.
Prefer identifiers over prose for automation. A client can switch on reason: creditlimitreached; it should not search detail for the word credit. Keep extension names portable and concise, and avoid nesting a complete legacy error envelope under error. That preserves the old model indefinitely and forces clients to understand two taxonomies. During migration, map each legacy code to one problem type and a limited set of typed extensions.
| Failure | HTTP status | Problem type decision | Useful extension | Client action |
|---|---|---|---|---|
| Malformed JSON | 400 | Usually one generic representation problem | Safe parse location if available | Correct request serialization |
| Field validation | 422 or API policy choice | Stable validation type | Array of field paths and rule IDs | Correct listed values |
| Version conflict | 409 | State-conflict type | Current revision or ETag | Refetch, merge and retry |
| Rate limit | 429 | Quota type if clients need domain detail | Quota class; keep timing in headers | Wait according to policy |
| Unavailable dependency | 503 | Generic temporary failure may suffice | Optional safe dependency class | Retry with backoff if operation is safe |
| Authorization policy | 403 | Type only when remediation differs | Required entitlement identifier | Request access or choose permitted action |
Set a strict security and privacy boundary
Problem details are part of the public interface, not an observability payload. Never include stack traces, SQL, filesystem paths, secret values, internal service addresses, policy expressions, or the existence of resources a caller is not allowed to discover. Validation messages should not echo entire submitted values. Apply the same redaction rules to instance, extension fields, logs, and examples. A random occurrence identifier can link the caller to protected server-side diagnostics without exposing those diagnostics.
Threat-model differences between authenticated and anonymous callers. A login problem should not reveal whether an account exists. An object lookup may need the same outward response for absent and unauthorized resources. Rate-limit detail should help legitimate clients without disclosing defenses that enable evasion. Review new problem types as API changes, including abuse cases and tenant-boundary leakage, rather than letting each exception handler invent fields.
Separate machine behavior from localization
title can be localized, and detail is human-readable, but neither should drive client logic. If localization is offered, use normal content negotiation, state the supported languages, set response metadata correctly, and test fallbacks. Keep type URIs, extension keys, reason identifiers, field paths, and numeric values language-neutral. A support portal may render richer guidance from the type and locale while an SDK exposes typed remediation fields.
Write detail text for the API caller, not the server developer. Good detail explains what is wrong in this occurrence and what can be changed, without promising a retry will succeed when it may not. Avoid vague messages such as something went wrong. Also avoid embedding support playbooks in every response; use the type documentation or a safe support reference when the action requires human investigation.
Model and test Problem Details in OpenAPI
Define a reusable base schema with the standard members, then compose controlled schemas for problem types or extension families. Model URI references, integers, arrays, and nullable values accurately. Give operations the status responses they can actually produce, including gateway and authentication failures that bypass application code. Examples should be valid against the declared schema and free of real identifiers. Document application/problem+json explicitly rather than calling the response ordinary JSON.
Contract tests should verify content type, actual status, matching body status when present, stable type, required extensions, redaction, and unknown-extension tolerance. Add negative tests for accidental stack traces and oversized validation arrays. Client libraries should have a generic fallback for an unknown type and malformed or non-problem error bodies, because proxies and older services may still respond differently.
Migrate legacy error envelopes without stranding clients
Inventory legacy codes and observed client decisions before mapping. Some codes may be aliases; others may hide materially different remediation. Publish a mapping, add Problem Details behind content negotiation or a version boundary, and instrument which representation consumers request. Avoid returning both envelopes in one body. That hybrid becomes a permanent ambiguity about which code, message, and status is authoritative.
Move clients first where possible: teach them to parse Problem Details, tolerate unknown types, and fall back safely. Then migrate producers by domain and monitor unknown-type rates, parsing failures, retry behavior, and support volume. Retire legacy formats only when the consumer inventory and traffic evidence support it. Keep the type registry independently versioned so documentation and ownership survive service reorganizations.
Key takeaways
- Create problem types from stable remediation semantics, not server exception classes.
- Keep the actual HTTP status authoritative and use established headers for protocol behavior.
- Make extensions typed, documented, optional where possible, and safe for unknown-field handling.
- Never expose internal diagnostics or resource existence through public problem responses.
- Keep identifiers machine-stable and human text localizable.
- Migrate with a code-to-type map, client fallback, telemetry, and a dated legacy retirement plan.
RFC 9457 Problem Details FAQ
Does every HTTP error need a custom problem type?
No. If the HTTP status and safe detail fully explain the failure, about:blank or a simple response may be sufficient. Add a type when stable additional semantics enable distinct remediation or cross-service consistency.
May a client parse the detail string?
It should not. Detail is human-readable and may change or be localized. Put machine decisions in the type URI and documented extension members, while clients retain an unknown-type fallback.
What if the body status differs from the response status?
Treat the HTTP response status as authoritative and record the mismatch as a producer or intermediary defect. RFC 9457 requires generators to align them; designing routine disagreement into clients conceals an operational problem.
Conclusion
RFC 9457 standardizes the shape of an API problem, while teams remain responsible for useful semantics. A small registry, correct HTTP behavior, safe typed extensions, clear remediation, contract tests, and measured migration create errors that clients can handle consistently. The result is not merely uniform JSON; it is a dependable failure interface that remains understandable across services and releases.