How do you return an RFC 7807 body from a Spring MVC controller or exception handler?
answer
- Return ProblemDetail or ResponseEntity<ProblemDetail>
- throw ErrorResponseException / ResponseStatusException
- spring.mvc.problemdetails.enabled=true
- extend ResponseEntityExceptionHandler
- @ControllerAdvice / @RestControllerAdvice
basics
~10 sReturn a ProblemDetail (or ResponseEntity<ProblemDetail>) from a @RestController method or an @ExceptionHandler in a @ControllerAdvice. You can also throw an ErrorResponseException, which Spring turns into a problem+json body.
solid answer
~30 sThere are three common ways. (1) Return a ProblemDetail (or ResponseEntity<ProblemDetail> for custom headers) directly from a controller or an @ExceptionHandler method; Spring's message converters serialize it as application/problem+json and use its status. (2) Throw an ErrorResponseException, a RuntimeException that carries a ProblemDetail, or the older ResponseStatusException — both implement the ErrorResponse contract, so Spring renders a problem body. (3) For all of Spring MVC's own exceptions (validation, unsupported media type, etc.), enable spring.mvc.problemdetails.enabled=true in Boot, which makes ResponseEntityExceptionHandler emit ProblemDetail bodies instead of the default error structure. Centralize app-specific handlers in a @ControllerAdvice extending ResponseEntityExceptionHandler.
code
java · 18 linesimport org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@ControllerAdvice
class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(OrderNotFoundException.class)
ProblemDetail handleNotFound(OrderNotFoundException ex) {
ProblemDetail pd = ProblemDetail.forStatusAndDetail(
HttpStatus.NOT_FOUND, ex.getMessage());
pd.setTitle("Order not found");
pd.setProperty("orderId", ex.getOrderId());
return pd; // rendered as application/problem+json, HTTP 404
}
}go deeper
Know you can return a ProblemDetail from a @RestControllerAdvice handler.
Know all three routes and the problemdetails.enabled property, plus extending ResponseEntityExceptionHandler.
Discuss overriding createProblemDetail/handleExceptionInternal to add cross-cutting fields uniformly.
Address rollout risk of changing wire format for all framework errors and client contract coordination.
## Three routes to a problem+json body ### 1. Return ProblemDetail from a handler Any `@RestController` method or `@ExceptionHandler` method can return a `ProblemDetail`. Spring's `HttpMessageConverter` for problem details serializes it and sets `Content-Type: application/problem+json`. The HTTP status comes from the ProblemDetail's `status` field when returned this way from an exception handler. ```java @RestControllerAdvice class ApiExceptionHandler { @ExceptionHandler(OrderNotFoundException.class) ProblemDetail handle(OrderNotFoundException ex) { ProblemDetail pd = ProblemDetail.forStatusAndDetail( HttpStatus.NOT_FOUND, ex.getMessage()); pd.setTitle("Order not found"); return pd; } } ``` If you need custom headers, wrap it: `ResponseEntity.of(pd).build()` or `new ResponseEntity<>(pd, headers, HttpStatus.NOT_FOUND)`. ### 2. Throw an ErrorResponse-based exception `org.springframework.web.ErrorResponseException` is a ready-made `RuntimeException` that implements the `ErrorResponse` contract and holds a `ProblemDetail`: ```java throw new ErrorResponseException(HttpStatus.CONFLICT, ProblemDetail.forStatusAndDetail(HttpStatus.CONFLICT, "Version conflict"), null); ``` The older `ResponseStatusException` also implements `ErrorResponse`, so since Spring 6 it too produces a problem body (when problemdetails are enabled). Because these implement `ErrorResponse`, you don't need an explicit `@ExceptionHandler` — Spring's default handling renders them. ### 3. Turn Spring's built-in exceptions into problem bodies Spring MVC throws many framework exceptions (`MethodArgumentNotValidException`, `HttpMessageNotReadableException`, `HttpRequestMethodNotSupportedException`, …). By default Boot renders them via its generic error attributes. Set `spring.mvc.problemdetails.enabled=true` and `ResponseEntityExceptionHandler` will render all of them as RFC 7807 bodies. In WebFlux the analogous property is `spring.webflux.problemdetails.enabled=true`. ### Centralizing with ResponseEntityExceptionHandler `org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler` is an abstract base class for a `@ControllerAdvice`. It already has `@ExceptionHandler` methods for every standard Spring MVC exception and returns `ResponseEntity` with `ProblemDetail` bodies. Extend it and add your own handlers for domain exceptions: ```java @ControllerAdvice class GlobalExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler(OrderNotFoundException.class) ProblemDetail handleNotFound(OrderNotFoundException ex) { return ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage()); } } ``` You can also override `createProblemDetail(...)` or `handleExceptionInternal(...)` to customize the framework exceptions' bodies uniformly (add a correlation id, override titles, etc.). ## Gotchas - Returning `ProblemDetail` directly vs `ResponseEntity<ProblemDetail>`: the plain return relies on the `status` field for the HTTP status; use `ResponseEntity` when you need headers or want the status line explicit. - Enabling `problemdetails` changes the wire format of *all* framework errors — coordinate with clients before flipping it. - Don't put a `@ResponseStatus` on a controller method that also returns a `ProblemDetail` with a different status; keep them consistent.
- What does spring.mvc.problemdetails.enabled=true actually change?It makes ResponseEntityExceptionHandler render Spring MVC's built-in framework exceptions (validation, unsupported method, unreadable body, etc.) as RFC 7807 ProblemDetail bodies instead of Boot's default generic error JSON.
- When would you return ResponseEntity<ProblemDetail> instead of a bare ProblemDetail?When you need to set custom response headers (e.g. Retry-After) or want the HTTP status line set explicitly rather than derived from the ProblemDetail's status field.
saying these in an interview costs you the question
- Believing you must write a custom @ExceptionHandler for every Spring framework exception (ResponseEntityExceptionHandler already covers them)
- Thinking ProblemDetail bodies appear automatically without enabling the property for framework exceptions
- Confusing @ResponseStatus status with the ProblemDetail body status and setting them inconsistently