Skip to content
stateless.co · Engineering notes from the request/response layer
statelessThe engineering desk

A publication about the machinery under everyday software: the contracts between services, the queries behind a page, and the failures that only show up in production.

01APIs & Protocols

RFC 9457 Problem Details: Retiring Your Home-Grown Error JSON

When creating an error JSON, most engineers start by copying a format from a previous project. Over time, this casual practice leads to a proliferation of custom error shapes in every…

RFC 9457 Problem Details: Retiring Your Home-Grown Error JSON
Photo: Lklundin · CC BY-SA 4.0 · Wikimedia Commons
What is in this piece
  1. The Standard and Its Purpose
  2. The Five Base Members
  3. What the RFC Allows
  4. Why Type and Instance Are Not the Same Thing
  5. How to Trade a Bug Hunt for a Few Lines of Validation
  6. Solution: Migrate to the Format Automatically

RFC 9457, published in July 2023, is the 2023 successor to RFC 7807. The standard defines a JSON message body called application/problem+json to carry machine-readable details of HTTP API errors.

The Standard and Its Purpose

RFC 9457 exists to avoid having every API define its own custom error format. Just like application/json and application/xml, application/problem+json is a defined media type for HTTP APIs [FACT: The standard says a problem detail has five base members: type, title, status, detail, and instance, SOURCE: RFC Editor, https://www.rfc-editor.org/rfc/rfc9457. rfc-editor.org/rfc/rfc9457.html, 2023-07-31]

The Five Base Members

The RFC defines a JSON object with exactly five required members:

  1. Type: A URI reference that identifies the problem type. This is the key that lets a client infer the problem category. If omitted, the default value is about:blank. [FACT: The type member identifies the problem type and is a URI reference, SOURCE: RFC Editor, https://www.rfc-editor.org/rfc/rfc9457.html, 2023-07-31]
  2. Title: a short, human-readable summary of the problem type. It should not change from occurrence to occurrence of the problem.
  3. Status: The HTTP status code on the returned response. This is a public-standardized error code, but not semantically meaningful on its own.
  4. Instance: A URI reference that identifies the problem occurrence, as opposed to the type which identifies the general category. The instance distinguishes one problem occurrence from another.
  5. Detail: a human-readable explanation specific to this occurrence of the problem.

What the RFC Allows

Just like a the RFC anticipates that a service will need to attach extra data to the error. RFC 9457 permits different fields for problem instances. The included example shows cause and invalid-params members. Extensions simply need to be JSON strings or arrays.

Why Type and Instance Are Not the Same Thing

The instance is not the same as the type. An application might get the same error for different things. For instance, an invalid API key error raised on trying to check the status of API key #1924, still means the same thing as one raised on trying to remove API key #3316. But the two occurrences are different and need a way to refer to them.

A service that draws data from databases might get a timeout error looking up a customer record for prospering.com, and then later get a timeout error looking up an API key for rival companystore.com. Both are timeouts, but the occurrences are different and need their identification per instance (and not just implying a timeout).

The same happens with any repetitive task, like getting an ambiguous response from a vendor support bot, or getting a zero-error response from command line.

How to Trade a Bug Hunt for a Few Lines of Validation

If the problem pattern is missing required data, RFC 9457 actually discourages validation messages getting dumped into the detail field, as in:

{
 "type": "https://httpstatuses.com/400",
 "title": "Bad Request",
 "status": 400, "detail": "Missing parameter: user",
 "instance": "A...
 "errorDetails": {
 "property": "user"
 }
}

Also, RFC 9457 separates the error's category or international problem code from the specific instance of the error. This separates the semantic problem from the data sent, which is key when making error handling easy.

{
 "type": "https://autodoc.elementor.com/problem-type/required",
 "title": "Missing parameter",
 "status": 400,
 "detail": "Missing parameter on object missingProperty.",
 "mainPropertyValidations": {
 "values": {
 "key": "value1"
 }
 }
}

In Summary, RFC 9457 discourages a client-side bug hunt, by separating the identification of the error type from occurences of the error. During implementation, the major concept a service must map is how its homegrown error lookup gets broken up into type as problem category and instance as occurrence.

Solution: Migrate to the Format Automatically

RFC 9457 solves the error-to-software imbalance. It is a standardized media type that defines a consistent response format to signal error situations. The RFC defines five members in the JSON response to describe the problem and provides the format for defining extensions.

The real solution is to map the service error lookup to the standardized media-type response structure.

Implementing RFC 9457 requires identifying each error and associating it to a common problem. This involves structuring the application's error logic using frameworks or packages that support the RFC 9457 standard.

Lastly, this leaves the question of migrating the custom error formats to RFC 9457. This mainly includes defining the problem types, writing the error handlers to structure the response, and ensuring the error instances are identified properly. The migration process may involve identifying and refactoring the error handling code across the application.