I have multipler classes annotated with @ControllerAdvice
, each with an @ExceptionHandler
method in.
One handles Exception
with the intention that if no more specific handler is found, this should be used.
Sadly Spring MVC appears to be always using the most generic case (Exception
) rather than more specific ones (IOException
for example).
Is this how one would expect Spring MVC to behave? I'm trying to emulate a pattern from Jersey, which assesses each ExceptionMapper
(equivalent component) to determine how far the declared type that it handles is from the exception that has been thrown, and always uses the nearest ancestor.
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, aHandlerExceptionResolverComposite
, whichThose other resolvers are
ExceptionHandlerExceptionResolver
ResponseStatusExceptionResolver
DefaultHandlerExceptionResolver
registered in that order. For the purpose of this question we only care about
ExceptionHandlerExceptionResolver
.At context initialization, Spring will generate a
ControllerAdviceBean
for each@ControllerAdvice
annotated class it detects. TheExceptionHandlerExceptionResolver
will retrieve these from the context, and sort them using usingAnnotationAwareOrderComparator
whichIt'll then register an
ExceptionHandlerMethodResolver
for each of theseControllerAdviceBean
instances (mapping available@ExceptionHandler
methods to the exception types they're meant to handle). These are finally added in the same order to aLinkedHashMap
(which preserves iteration order).When an exception occurs, the
ExceptionHandlerExceptionResolver
will iterate through theseExceptionHandlerMethodResolver
and use the first one that can handle the exception.So the point here is: if you have a
@ControllerAdvice
with an@ExceptionHandler
forException
that gets registered before another@ControllerAdvice
class with an@ExceptionHandler
for a more specific exception, likeIOException
, that first one will get called. As mentioned earlier, you can control that registration order by having your@ControllerAdvice
annotated class implementOrdered
or annotating it with@Order
or@Priority
and giving it an appropriate value.I also found in the documentation that :
So this means that if you want to solve this issue, you will need to add your specific exception handler within the controller throwing those exception. ANd to define one and only ControllerAdvice handling the Global default exception handler.
This simplies the process and we don't need the Order annotation to handle the problem.
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.
Enjoy!
The order of exception handlers can be changed using the
@Order
annotation.For example:
@Order
's value can be any integer.There's a similar situation convered in the excellent "Exception Handling in Spring MVC" post on the Spring blog, in the section entitled Global Exception Handling. Their scenario involves checking for ResponseStatus annotations registered on the exception class, and if present, rethrowing the exception to let the framework handle them. You might be able to use this general tactic - try to determine if there is a might be a more appropriate handler out there and rethrowing.
Alternatively, there's some other exception handling strategies covered that you might look at instead.