Setting Precedence of Multiple @ControllerAdvice @ExceptionHandlers
Is this how one would expect Spring MVC to behave?
As of Spring 4.3.7, here's how Spring MVC behaves: it uses HandlerExceptionResolver
instances to handle exceptions thrown by handler methods.
By default, the web MVC configuration registers a single HandlerExceptionResolver
bean, a HandlerExceptionResolverComposite
, which
delegates to a list of other
HandlerExceptionResolvers
.
Those other resolvers are
ExceptionHandlerExceptionResolver
ResponseStatusExceptionResolver
DefaultHandlerExceptionResolver
registered in that order. For the purpose of this question we only care about ExceptionHandlerExceptionResolver
.
An
AbstractHandlerMethodExceptionResolver
that resolves exceptions through@ExceptionHandler
methods.
At context initialization, Spring will generate a ControllerAdviceBean
for each @ControllerAdvice
annotated class it detects. The ExceptionHandlerExceptionResolver
will retrieve these from the context, and sort them using using AnnotationAwareOrderComparator
which
is an extension of
OrderComparator
that supports Spring'sOrdered
interface as well as the@Order
and@Priority
annotations, with an order value provided by an Ordered instance overriding a statically defined annotation value (if any).
It'll then register an ExceptionHandlerMethodResolver
for each of these ControllerAdviceBean
instances (mapping available @ExceptionHandler
methods to the exception types they're meant to handle). These are finally added in the same order to a LinkedHashMap
(which preserves iteration order).
When an exception occurs, the ExceptionHandlerExceptionResolver
will iterate through these ExceptionHandlerMethodResolver
and use the first one that can handle the exception.
So the point here is: if you have a @ControllerAdvice
with an @ExceptionHandler
for Exception
that gets registered before another @ControllerAdvice
class with an @ExceptionHandler
for a more specific exception, like IOException
, that first one will get called. As mentioned earlier, you can control that registration order by having your @ControllerAdvice
annotated class implement Ordered
or annotating it with @Order
or @Priority
and giving it an appropriate value.
Sotirios Delimanolis was very helpful in his answer, on further investigation we found that, in spring 3.2.4 anyway, the code that looks for @ControllerAdvice annotations also checks for the presence of @Order annotations and sorts the list of ControllerAdviceBeans.
The resulting default order for all controllers without the @Order annotation is Ordered#LOWEST_PRECEDENCE which means if you have one controller that needs to be the lowest priority then ALL your controllers need to have a higher order.
Here's an example showing how to have two exception handler classes with ControllerAdvice and Order annotations that can serve appropriate responses when either a UserProfileException or RuntimeException occurs.
class UserProfileException extends RuntimeException {
}
@ControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
class UserProfileExceptionHandler {
@ExceptionHandler(UserProfileException)
@ResponseBody
ResponseEntity<ErrorResponse> handleUserProfileException() {
....
}
}
@ControllerAdvice
@Order(Ordered.LOWEST_PRECEDENCE)
class DefaultExceptionHandler {
@ExceptionHandler(RuntimeException)
@ResponseBody
ResponseEntity<ErrorResponse> handleRuntimeException() {
....
}
}
- See ControllerAdviceBean#initOrderFromBeanType()
- See ControllerAdviceBean#findAnnotatedBeans()
- See ExceptionHandlerExceptionResolver#initExceptionHandlerAdviceCache()
Enjoy!
The order of exception handlers can be changed using the @Order
annotation.
For example:
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.annotation.ControllerAdvice;
@ControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CustomExceptionHandler {
//...
}
@Order
's value can be any integer.