Companion code for "Global Exception Handling with ProblemDetail (RFC 9457) in Spring Boot 4". Thirteen failures under five handling setups (Boot defaults, the Boot flag, a ResponseEntityExceptionHandler advice, advice plus an ErrorController, a catch-all ordered first), validation errors, i18n, content negotiation, Security's 401/403, silent 500s and decoding on the client. 16 tests pin the behaviour. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01C3TETMrqVUWeFkNtz3Jbo3
4.7 KiB
2. Choosing a mechanism
← 1. Mental model · Index · Next: 3. Validation errors →
The same thirteen failures under five setups - matrix-summary.txt,
produced by scripts/demo-matrix.sh:
failure | defaults | boot-flag | advice | advice,errors | catchall-first
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
domain exception (OrderNotFoundException) | 500 Boot /error JSON | 500 Boot /error JSON | 404 problem+json | 404 problem+json | 500 problem+json
type mismatch (/orders/abc) | 400 Boot /error JSON | 400 problem+json | 400 problem+json | 400 problem+json | 500 problem+json
exception in a servlet filter | 500 Boot /error JSON | 500 Boot /error JSON | 500 Boot /error JSON | 500 problem+json | 500 Boot /error JSON
401 no credentials | 401 Boot /error JSON | 401 Boot /error JSON | 401 problem+json | 401 problem+json | 401 Boot /error JSON
spring.mvc.problemdetails.enabled
Defaults to false in Spring Boot 4.1.1 (spring-configuration-metadata.json in
spring-boot-webmvc). When true, WebMvcAutoConfiguration$ProblemDetailsErrorHandlingConfiguration
registers ProblemDetailsExceptionHandler - an empty subclass of ResponseEntityExceptionHandler -
with:
@ConditionalOnMissingBean(ResponseEntityExceptionHandler.class)- it backs off as soon as you declare your own subclass (advice-order.txt, profilesboot-flag,advice)@Order(0)- it is consulted before any advice with the default (lowest) precedence
It handles exactly what ResponseEntityExceptionHandler handles: Spring MVC's exceptions and any
ErrorResponse. It does not handle your exceptions, which is why the first and tenth rows stay
Boot's JSON. Turning the flag on and stopping there gives an API with two error shapes.
Advice ordering, and the catch-all trap
ExceptionHandlerExceptionResolver walks the advices in order and uses the first advice that has
any matching handler, not the most specific handler across all advices. The
catchall-first profile
declares @ExceptionHandler(Exception.class) at Ordered.HIGHEST_PRECEDENCE; it matches
NoResourceFoundException, HttpRequestMethodNotSupportedException and every other framework
exception before Boot's handler at order 0 gets a look. Every 4xx in the matrix becomes a 500.
Keep the catch-all in the same class as your specific handlers (the most specific handler wins within one class), or give it the lowest precedence.
The handler that stops the application starting
Extending ResponseEntityExceptionHandler and adding
@ExceptionHandler(MethodArgumentNotValidException.class) - the obvious way to customise
validation errors - fails at startup (ambiguous-handler.txt):
Caused by: java.lang.IllegalStateException: Ambiguous @ExceptionHandler method mapped for [ExceptionHandler{exceptionType=org.springframework.web.bind.MethodArgumentNotValidException, mediaType=*/*}]:
{org.springframework.http.ProblemDetail com.ankurm.problems.advice.AmbiguousExceptionHandler.invalid(org.springframework.web.bind.MethodArgumentNotValidException),
public final org.springframework.http.ResponseEntity org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler.handleException(java.lang.Exception,org.springframework.web.context.request.WebRequest) throws java.lang.Exception}
The base class's handleException is final and already mapped. Override the protected
handleMethodArgumentNotValid(...) instead - chapter 3.
What the recommended setup is
advice + errors:
GlobalExceptionHandlerextendsResponseEntityExceptionHandler, adds domain handlers and a logging catch-allProblemDetailErrorControllercovers the container error page - chapter 6ProblemDetailSecurityHandlerscovers 401/403 with aWWW-Authenticateheader intact
AdviceContractTest pins it.