Standard error responses with ProblemDetail
RFC 7807, so every service in the estate returns errors in the same shape.
Open this lesson in the learning hubKey points
- Hand-rolled error bodies drift. One service returns
message, anothererror, a third a bare string - and every client writes its own parsing. - RFC 7807 defines
application/problem+jsonwith a fixed core:type,title,status,detailandinstance, plus any extra fields you add. - Spring Framework 6 ships
ProblemDetailand can produce it for built-in exceptions - setspring.mvc.problemdetails.enabled=trueand validation and 404 responses adopt the format. - Extending
ResponseEntityExceptionHandlergives you the built-in handling and lets you override individual exceptions rather than reimplementing all of them. - Put a stable, documented URI in
type. That is the field clients branch on - never the human-readabletitle, which you will want to reword. - Keep internal detail out of
detail. A stack trace or SQL fragment in an error body is an information leak, and it will end up in a customer log.
Example
@RestControllerAdvice
class ApiExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(InsufficientFundsException.class)
ProblemDetail handleFunds(InsufficientFundsException ex) {
ProblemDetail pd = ProblemDetail.forStatusAndDetail(
HttpStatus.CONFLICT, "Balance is lower than the requested amount.");
// Stable and documented - this is what clients switch on.
pd.setType(URI.create("https://api.example.com/problems/insufficient-funds"));
pd.setTitle("Insufficient funds");
// Extra members are allowed and are where the useful context goes.
pd.setProperty("accountId", ex.accountId());
pd.setProperty("shortfall", ex.shortfall());
pd.setProperty("traceId", MDC.get("traceId"));
return pd;
}
}
/*
* HTTP/1.1 409 Conflict
* Content-Type: application/problem+json
*
* {
* "type": "https://api.example.com/problems/insufficient-funds",
* "title": "Insufficient funds",
* "status": 409,
* "detail": "Balance is lower than the requested amount.",
* "instance": "/accounts/42/withdrawals",
* "accountId": 42,
* "shortfall": 25.50,
* "traceId": "8f3c1a..."
* }
*/
One error shape across every service, with a stable type URI clients can branch on and no internals leaked in the body.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Spring Boot course, and every lesson in it is listed on the Spring Boot contents page.