Back to Error Handling & Problem Details
Q1Question 1 of 1

What is the RFC 9457 "Problem Details" standard for HTTP errors

Key Concepts
  • RFC 9457 Problem Details is the IETF standard for HTTP error responses using Content-Type application/problem+json.
  • The type field is a stable URL that clients switch on; it must never change once published.
  • The title and detail fields are human-readable and can be reworded or localised without breaking clients.
  • Extension members like traceId can be added to the standard shape for debugging.
  • Spring supports Problem Details natively through the ProblemDetail class and ErrorResponse interface.
  • Adopting the standard means one parser works for all services, eliminating bespoke error shapes.

Definition

RFC 9457 Problem Details is the standard application/problem+json error shape with type, title, status, detail, instance, and extensions, natively supported by Spring's ProblemDetail class.
  • RFC 9457 – the IETF standard (replacing RFC 7807) defining a JSON format for HTTP error responses called Problem Details.
  • Media type – application/problem+json, which signals to the client that the body is a standard problem document.
  • Fields – type (a stable URL for client switching), title (short summary), status (HTTP code in body), detail (human explanation), instance (URI for this occurrence).
  • Extensions – custom fields like traceId can be added to the standard shape.
  • Spring support – the ProblemDetail class and ErrorResponse interface let you build and return problem documents natively.
  • Why standard – one parser works for all services; new engineers already know it; gateways and tools understand it.

Think of National Bank of India agreeing with every partner bank on one standard rejection form. Instead of each department inventing its own error slip, everyone uses the same printed form: a reference code box (type), a one-line reason (title), the official rejection stamp number (status), a detailed explanation box (detail), and a case number for this exact rejection (instance). Because it is a standard form, every partner's software already knows how to read it without a custom manual.

Understanding the Concepts

What is RFC 9457 Problem Details?

RFC 9457 defines a standard JSON format for HTTP error responses. It replaces RFC 7807 with minor updates. The purpose is to give every error a consistent, machine-readable shape so one parser works across all services and tools.

The response uses the media type application/problem+json, which tells the client the body is a standard problem document, not a normal resource.

JAVA
// A Problem Details response:
// HTTP/1.1 422 Unprocessable Entity
// Content-Type: application/problem+json
// {
//   "type": "https://api.nbi.in/problems/insufficient-funds",
//   "title": "Insufficient Funds",
//   "status": 422,
//   "detail": "Balance 250000 is less than requested 900000",
//   "instance": "/api/v1/transfers/req-abc123",
//   "traceId": "a1b2c3d4"
// }

What are the standard fields?

Each field has a defined role in the standard.

  • type – a URI, usually a stable URL like https://api.nbi.in/problems/insufficient-funds. This is the field clients switch on. It plays the same role as a stable error code.
  • title – a short, human-readable summary that stays the same for a given type.
  • status – the HTTP status code repeated in the body, convenient for logging.
  • detail – a human-readable explanation specific to this occurrence.
  • instance – a URI identifying this specific occurrence, often a request id or a link to a log entry.
JAVA
// The type URL is stable and switchable – clients branch on it.
// The detail is human text that can change without breaking clients.
// The instance is a unique identifier for this specific error.

How does Spring support Problem Details?

Spring Boot supports Problem Details natively through the ProblemDetail class and the ErrorResponse interface. You create a ProblemDetail, set the fields, add extension properties like traceId, and return it. Spring automatically sets the content type to application/problem+json.

JAVA
package com.bank.exception;

import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.slf4j.MDC;
import java.net.URI;

@RestControllerAdvice
public class ProblemDetailsHandler {

    @ExceptionHandler(InsufficientFundsException.class)
    public ProblemDetail handleInsufficient(InsufficientFundsException e) {
        ProblemDetail pd = ProblemDetail.forStatusAndDetail(
                HttpStatus.UNPROCESSABLE_ENTITY, e.getMessage());
        pd.setType(URI.create("https://api.nbi.in/problems/insufficient-funds"));
        pd.setTitle("Insufficient Funds");
        pd.setProperty("traceId", MDC.get("traceId"));
        return pd; // Spring sets Content-Type: application/problem+json
    }

    @ExceptionHandler(AccountNotFoundException.class)
    public ProblemDetail handleNotFound(AccountNotFoundException e) {
        ProblemDetail pd = ProblemDetail.forStatusAndDetail(
                HttpStatus.NOT_FOUND, e.getMessage());
        pd.setType(URI.create("https://api.nbi.in/problems/account-not-found"));
        pd.setTitle("Account Not Found");
        pd.setProperty("traceId", MDC.get("traceId"));
        return pd;
    }
}

How do you enable Problem Details for Spring's built-in exceptions?

Spring can also produce problem documents for its own built-in exceptions, like a 404 for a missing handler or a 405 for a wrong method. You enable this in application.properties:

JAVA
// application.properties:
// spring.mvc.problemdetails.enabled=true

// With this enabled, Spring's own 404, 405, and other built-in errors
// also return Content-Type: application/problem+json.

Why prefer Problem Details over a hand-rolled error shape?

A hand-rolled error shape has to be invented, documented, and maintained, and every client must learn it separately. Problem Details is a documented, framework-supported contract: new engineers already know it, API documentation tools render it, and clients across languages have libraries for it.

| Aspect | Hand-rolled error shape | RFC 9457 Problem Details |
|---|---|---|
| Standard | No – each team invents their own | Yes – IETF standard |
| Client libraries | Must write custom parser | Libraries exist in most languages |
| Gateway support | Gateways cannot understand it | Gateways can parse it natively |
| Spring support | Must build from scratch | ProblemDetail class built-in |
| Learning curve | New engineers must read your docs | New engineers already know RFC 9457 |
| Maintenance | Must maintain and evolve yourself | Maintained by the IETF standard |

JAVA
// Hand-rolled (must document, maintain, teach):
public record ApiError(String code, String message, String traceId) {}

// RFC 9457 (standard, supported, known):
ProblemDetail pd = ProblemDetail.forStatusAndDetail(status, detail);
pd.setType(URI.create("https://api.nbi.in/problems/insufficient-funds"));
pd.setProperty("traceId", MDC.get("traceId"));

What is the role of the type URL?

The type URL must be stable and is what clients switch on, never the title or detail. The title and detail are human text that can be reworded or localised without breaking clients. The type URL does not need to resolve to a live page, though making it a documentation link is good practice. Once published, it must never change, because it is part of the API contract.

JAVA
// Client code switches on type URL, not on title or detail.
if ("https://api.nbi.in/problems/insufficient-funds".equals(problem.getType())) {
    showAddMoneyButton();
}
// The detail can change from "Balance too low" to "Your balance is insufficient"
// without breaking any client.

How do you add extension members?

Extension members are custom fields added to the standard shape using setProperty(). Common extensions include traceId, accountId, timestamp, and domain-specific details.

JAVA
ProblemDetail pd = ProblemDetail.forStatusAndDetail(
        HttpStatus.UNPROCESSABLE_ENTITY, "Balance 250000 < requested 900000");
pd.setType(URI.create("https://api.nbi.in/problems/insufficient-funds"));
pd.setTitle("Insufficient Funds");
pd.setProperty("traceId", "a1b2c3d4");
pd.setProperty("accountId", "SAV2001");
pd.setProperty("currentBalance", 250000);
pd.setProperty("requestedAmount", 900000);

What are common mistakes with Problem Details?

A common mistake is making the type URL unstable, so renaming it breaks clients. Another is using title or detail as the machine-readable code. A third is not adding a traceId extension, making it hard for support to trace errors.

JAVA
// Wrong: type changes whenever someone renames the problem.
pd.setType(URI.create("https://api.nbi.in/problems/not-enough-money")); // Was "insufficient-funds"!
// Client code that switched on the old type URL now breaks.

// Correct: type URL is stable, documented, and never renamed.
pd.setType(URI.create("https://api.nbi.in/problems/insufficient-funds")); // Permanent.

What should a senior engineer know about Problem Details?

A senior engineer knows that the type URL is part of the API contract, like endpoint URLs. They know that adopting Problem Details across all microservices means one parser for every service, eliminating bespoke error shapes. They also know that the ErrorResponse interface in Spring lets custom exceptions carry their own ProblemDetail, so even exceptions thrown deep in the service layer produce standard problem documents.

Visual Overview

DIAGRAM
┌──────────────────────────────────────────────────────────┐
│         RFC 9457 Problem Details Structure                 │
├──────────────────────────────────────────────────────────┤
│                                                          │
│  Content-Type: application/problem+json                  │
│                                                          │
│  ┌──────────────────────────────────────────────┐       │
│  │ type:     "https://api.nbi.in/.../insuf..."  │ ◀─ stable, client switches on this│
│  │ title:    "Insufficient Funds"                │ ◀─ human summary, same per type│
│  │ status:   422                                 │ ◀─ HTTP code in body           │
│  │ detail:   "Balance 250000 < requested 900000"│ ◀─ human, this occurrence      │
│  │ instance: "/api/v1/transfers/req-abc123"      │ ◀─ unique ID for this error    │
│  │ traceId:  "a1b2c3d4"                          │ ◀─ extension: find in logs     │
│  └──────────────────────────────────────────────┘       │
│                                                          │
│  Spring Flow:                                            │
│  Exception thrown                                        │
│       │                                                  │
│       ▼                                                  │
│  @RestControllerAdvice                                   │
│  @ExceptionHandler(InsufficientFundsException.class)     │
│       │                                                  │
│       ▼                                                  │
│  ProblemDetail.forStatusAndDetail(422, msg)               │
│  pd.setType(...)                                         │
│  pd.setTitle(...)                                        │
│  pd.setProperty("traceId", ...)                          │
│       │                                                  │
│       ▼                                                  │
│  Return ProblemDetail → Spring sets Content-Type         │
│  automatically to application/problem+json               │
└──────────────────────────────────────────────────────────┘

Key Takeaway

RFC 9457, replacing RFC 7807, is the IETF standard format for HTTP error responses called Problem Details. The response uses Content-Type: application/problem+json and a defined set of fields: type is a stable URL identifying the kind of problem and is the field clients switch on; title is a short human summary; status repeats the HTTP status in the body; detail is a human explanation specific to this occurrence; and instance identifies this particular occurrence. Extension members like traceId can be added.

Spring supports this natively through the ProblemDetail class and the ErrorResponse interface. You build a ProblemDetail with forStatusAndDetail(), set the stable type URL, the title, and extension properties, and return it from an exception handler. Spring sets the content type automatically, and with spring.mvc.problemdetails.enabled=true, even Spring's built-in exceptions produce problem documents.

The reason to prefer it over a hand-rolled shape is that it is a documented, framework-supported contract. At National Bank of India, five microservices each had their own error JSON, so the mobile app carried five separate parsers. Adopting Problem Details meant every service returned the same standard shape, the app used one parser, and new engineers already understood it because it is an IETF standard with library support across languages.

The detail to stress most is that the type URL must be stable and is what clients switch on. The title and detail are human text that can be reworded or localised. Once published, the type URL must never change, because it is part of the API contract. That stability is what let the app reliably show an "add money" button for the insufficient-funds type while the team freely improved the human wording.

Remember one sentence: RFC 9457 Problem Details gives every error a standard shape with a stable type URL for client logic and human-readable fields for display, supported natively by Spring's ProblemDetail class.

---

Share