Skip to content
Muhammet Şafak
tr
Asked by: Serkan Answered:

Should I move my API error bodies to the RFC 7807 problem+json format?


Question

We have a platform where each service (some Laravel, some Go) returns errors in a slightly different body: one uses `{"error": "..."}`, another `{"message": "...", "code": 42}`, a third a nested `errors` object. The client-side teams have to parse each service's errors separately and it's become unsustainable. Would it make sense to move the error bodies to the RFC 7807 `application/problem+json` format to establish a single contract, or does it just add unnecessary standards overhead?

Answer

Short answer: yes, standardize.

Short answer

But two conditions: base it on RFC 9457 (which updates/obsoletes 7807) rather than 7807 itself, and enforce it in one central layer, not per-endpoint by hand.

Why

  1. 7807 is now RFC 9457. Same shape, refreshed in 2023. Reference 9457 in new work. The fields are the same: type (URI), title, status, detail, instance — plus as many extra (extension) members as you like.

  2. The real win is the uniform envelope, not the field names. Client teams now parse a single shape across every service. That was exactly your pain, and it’s the whole value of the standard. So yes, it’s worth doing.

What to do

  1. Set the content type correctly: application/problem+json. So clients and intermediary proxies can distinguish it from a normal body. Forgetting this header is implementing the standard halfway.

  2. type is a stable machine key — treat it as a contract. Use a URN or a documentation URL. Clients branch on type, not on human-readable fields like title/detail. Don’t leak stack traces or internals into detail; that’s a human-readable sentence for the caller.

    {
      "type": "https://api.example.com/problems/insufficient-funds",
      "title": "Insufficient funds",
      "status": 422,
      "detail": "Wallet 7f3a has balance 12.00, needs 50.00.",
      "instance": "/wallets/7f3a/withdrawals/9910"
    }
  3. Enforce it in one place — the exception handler / middleware. In Laravel, produce it in the Handler’s render layer (since Laravel 11, that’s withExceptions()->render() in bootstrap/app.php instead); in Go, in a central “error → problem” mapper. Write it per-endpoint and it drifts over time, defeating the point of the standard.

  4. Validation errors need an extension. 9457 doesn’t define a field-level error list. Add an errors array extension for validation and fix that shape once across all services — otherwise it’ll diverge per service again.

  5. Plan the migration to be backward compatible. Existing clients may expect the old body. Switching the server side is easy, but clients don’t update overnight. Tie the new problem+json format to an API version, or during a transition window return both fields (e.g. the old message alongside the new detail) for a while, and drop the old one as clients migrate.

  6. Don’t over-standardize everything. problem+json is for error bodies; it doesn’t change the shape of successful responses. And automate the docs too: reference the type catalog in your OpenAPI schema so clients can see which error types to expect from the contract itself.

Bottom line: personally I’d base it on 9457, define a versionable URI scheme for type, consolidate production in a single exception handler, and define a shared errors extension for validation. I’d also migrate gradually — filling out the type catalog and moving clients over as I go — rather than in one big bang. The value of the standard is in the discipline: one envelope, one production point, type managed like a contract.

Related Reading

Share:

Comments

Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.

More Questions

All questions

Search the site

Start typing to search posts, projects and pages.

Esc to close Powered by Pagefind