Careeroza
ProblemDetail
A Standard Error Format
RFC 9457 "Problem Details for HTTP APIs" (it replaced RFC 7807) defines a standard JSON error body with content type application/problem+json. Spring 6 / Boot 3 supports it with the ProblemDetail class.
| Field | Meaning | Example |
|---|---|---|
type | URI that identifies the error kind | https://shopease.in/errors/not-found |
title | Short, human-readable summary | Product not found |
status | HTTP status code | 404 |
detail | Explanation for this occurrence | Product 42 not found |
instance | The request path | /api/products/42 |
| extra properties | Anything you add | errors, timestamp, traceId |
Returning ProblemDetail
@ExceptionHandler(ResourceNotFoundException.class)
public ProblemDetail notFound(ResourceNotFoundException ex) {
ProblemDetail pd = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
pd.setTitle("Resource not found");
pd.setType(URI.create("https://shopease.in/errors/not-found"));
pd.setProperty("timestamp", Instant.now());
return pd; // status comes from the ProblemDetail; instance is filled in by Spring
}Example
curl -i localhost:8080/api/products/42Example output
HTTP/1.1 404 Not Found
Content-Type: application/problem+json
{"type":"https://shopease.in/errors/not-found","title":"Resource not found","status":404,"detail":"Product 42 not found","instance":"/api/products/42","timestamp":"2026-10-12T10:31:07.551Z"}Extending ResponseEntityExceptionHandler
Spring's own exceptions (bad JSON, 405, 415, validation) can also be turned into ProblemDetail by extending ResponseEntityExceptionHandler. Override only what you want to customize:
@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex, HttpHeaders headers,
HttpStatusCode status, WebRequest request) {
ProblemDetail pd = ex.getBody(); // already a 400 ProblemDetail
pd.setTitle("Validation failed");
Map<String, String> errors = new TreeMap<>();
ex.getBindingResult().getFieldErrors()
.forEach(e -> errors.putIfAbsent(e.getField(), e.getDefaultMessage()));
pd.setProperty("errors", errors);
return ResponseEntity.badRequest().body(pd);
}
}Note: Do not declare an @ExceptionHandler(MethodArgumentNotValidException.class) in a class that extends ResponseEntityExceptionHandler: it already handles that exception and Spring fails at startup with "Ambiguous @ExceptionHandler method".