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.

FieldMeaningExample
typeURI that identifies the error kindhttps://shopease.in/errors/not-found
titleShort, human-readable summaryProduct not found
statusHTTP status code404
detailExplanation for this occurrenceProduct 42 not found
instanceThe request path/api/products/42
extra propertiesAnything you adderrors, 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/42

Example 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".