When developing REST APIs, error response formats tend to vary across teams and projects — you’ll see things like { "error": "Not Found" } or { "message": "...", "code": 404 }.

Spring Boot 3.x now includes built-in support for RFC 9457 (Problem Details for HTTP APIs), allowing you to switch to a unified error response format with a single configuration change. This guide walks through practical usage assuming you already have a @ControllerAdvice-based implementation in place.

What is RFC 9457 (Problem Details)?

It is a specification that standardizes error response formats for HTTP APIs. It succeeds RFC 7807, though the practical differences are minimal.

The key distinction from custom formats is the use of Content-Type: application/problem+json. Clients can use this Content-Type to identify an error response.

The main response fields are as follows:

FieldDescription
typeA URI identifying the error type
titleA short description of the error
statusThe HTTP status code
detailA specific explanation
instanceThe URI of the resource where the error occurred

Enabling Problem Details in Spring Boot 3.x

The ProblemDetail class has been included in Spring Boot since version 3.0. Start by adding a single line to application.properties:

spring.mvc.problemdetails.enabled=true

That’s all it takes. Spring MVC will now return all standard exceptions — such as NoHandlerFoundException and HttpMessageNotReadableException — in application/problem+json format.

Here is a comparison of 404 responses before and after enabling this setting.

Before (Spring Boot default):

{
  "timestamp": "2026-04-18T10:00:00.000+00:00",
  "status": 404,
  "error": "Not Found",
  "path": "/api/users/999"
}

After (RFC 9457 compliant):

{
  "type": "about:blank",
  "title": "Not Found",
  "status": 404,
  "detail": "No static resource api/users/999.",
  "instance": "/api/users/999"
}

Basic Usage of the ProblemDetail Class

The basic pattern is to create and return a ProblemDetail inside an @ExceptionHandler.

@ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ProblemDetail> handleUserNotFound(
        UserNotFoundException ex, HttpServletRequest request) {
    ProblemDetail problem = ProblemDetail.forStatusAndDetail(
        HttpStatus.NOT_FOUND, ex.getMessage()
    );
    problem.setTitle("ユーザーが見つかりません");
    problem.setType(URI.create("https://api.example.com/errors/user-not-found"));
    problem.setInstance(URI.create(request.getRequestURI()));
    return ResponseEntity.status(HttpStatus.NOT_FOUND).body(problem);
}

ProblemDetail.forStatus(HttpStatus) creates an instance without a detail message, while forStatusAndDetail(HttpStatus, String) lets you pass one in. Simple and intuitive.

@ControllerAdvice Extending ResponseEntityExceptionHandler

Updating your existing @ControllerAdvice to extend ResponseEntityExceptionHandler lets you handle all standard Spring MVC exceptions with Problem Details in one place.

@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<ProblemDetail> handleUserNotFound(
            UserNotFoundException ex, HttpServletRequest request) {
        ProblemDetail problem = ProblemDetail.forStatusAndDetail(
            HttpStatus.NOT_FOUND, ex.getMessage()
        );
        problem.setTitle("ユーザーが見つかりません");
        problem.setInstance(URI.create(request.getRequestURI()));
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(problem);
    }
}

Simply extending ResponseEntityExceptionHandler is enough for standard exceptions like MethodArgumentNotValidException to be automatically handled as ProblemDetail. For the differences from a plain @ControllerAdvice and a deeper look at error handling fundamentals, see Spring Boot REST API Error Handling.

Implementing ErrorResponse on Custom Exception Classes

Having your exception class implement the ErrorResponse interface allows the exception itself to carry ProblemDetail information directly. The simplest approach is to extend ErrorResponseException.

public class UserNotFoundException extends ErrorResponseException {

    public UserNotFoundException(Long userId) {
        super(HttpStatus.NOT_FOUND, createProblemDetail(userId), null);
    }

    private static ProblemDetail createProblemDetail(Long userId) {
        ProblemDetail problem = ProblemDetail.forStatusAndDetail(
            HttpStatus.NOT_FOUND,
            "ユーザーID " + userId + " は存在しません"
        );
        problem.setTitle("ユーザーが見つかりません");
        problem.setType(URI.create("https://api.example.com/errors/user-not-found"));
        return problem;
    }
}

With this structure, ResponseEntityExceptionHandler handles the exception automatically without needing individual handlers in your @ControllerAdvice.

Adding Extra Properties with Extensions

RFC 9457 allows additional properties beyond the standard fields. Use setProperty() to add them.

ProblemDetail problem = ProblemDetail.forStatusAndDetail(
    HttpStatus.INTERNAL_SERVER_ERROR, "予期しないエラーが発生しました"
);
problem.setProperty("errorCode", "SYS-001");
problem.setProperty("traceId", MDC.get("traceId"));

The response will look like this:

{
  "type": "about:blank",
  "title": "Internal Server Error",
  "status": 500,
  "detail": "予期しないエラーが発生しました",
  "instance": "/api/orders",
  "errorCode": "SYS-001",
  "traceId": "abc123def456"
}

Extra fields are part of your API contract with clients, so documenting them in your OpenAPI spec is good practice. For SwaggerUI integration setup, see the OpenAPI/Swagger integration guide.

Handling Validation Errors

With spring.mvc.problemdetails.enabled=true set, validation errors (MethodArgumentNotValidException) are also automatically returned as Problem Details.

{
  "type": "about:blank",
  "title": "Bad Request",
  "status": 400,
  "detail": "Invalid request content.",
  "instance": "/api/users",
  "errors": [
    {
      "object": "createUserRequest",
      "field": "email",
      "rejectedValue": "invalid-email",
      "message": "must be a well-formed email address"
    }
  ]
}

The list of field errors is included in the errors array. To customize the output, override ResponseEntityExceptionHandler#handleMethodArgumentNotValid. For details on implementing validation, see the @Valid annotation article.

Migration Patterns from Existing Formats

If you are considering migration from a custom error format, there are three main approaches.

Gradual migration (recommended) Use Problem Details for new endpoints while leaving existing ones as-is for now. This minimizes client impact and lets you proceed with reduced risk.

All-at-once migration Add spring.mvc.problemdetails.enabled=true and refactor your @ControllerAdvice in one pass. Best suited when the number of clients is small or you are releasing a new API version.

Coexistence pattern Switch the response format based on whether the Accept: application/problem+json header is present. The implementation cost is high, so this can generally be ruled out unless there is a compelling reason.

Summary

Key points for adopting Problem Details in Spring Boot 3.x:

  • Adding spring.mvc.problemdetails.enabled=true alone makes standard exceptions return RFC-compliant responses
  • ProblemDetail.forStatusAndDetail() provides a simple way to build error responses
  • Extending ResponseEntityExceptionHandler enables Problem Details for all standard exceptions at once
  • Extending ErrorResponseException lets domain exceptions carry their own ProblemDetail
  • setProperty() lets you flexibly add extra fields

The easiest first step is to try spring.mvc.problemdetails.enabled=true. If you are interested in localizing error messages, check out the i18n internationalization guide as well.