Question
I have multiple 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.
Answer
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
](http://docs.spring.io/spring/docs/current/javadoc-
api/org/springframework/web/servlet/HandlerExceptionResolver.html) instances
to handle exceptions thrown by handler methods.
By default, the web MVC configuration registers a single
HandlerExceptionResolver
bean, a
[HandlerExceptionResolverComposite
](http://docs.spring.io/spring/docs/current/javadoc-
api/org/springframework/web/servlet/handler/HandlerExceptionResolverComposite.html),
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
](http://docs.spring.io/spring/docs/current/javadoc-
api/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.html).
An
AbstractHandlerMethodExceptionResolver
that resolves exceptions through@ExceptionHandler
methods.
At context initialization, Spring will generate a
[ControllerAdviceBean
](http://docs.spring.io/spring/docs/current/javadoc-
api/org/springframework/web/method/ControllerAdviceBean.html) for each
[@ControllerAdvice
](http://docs.spring.io/spring/docs/current/javadoc-
api/org/springframework/web/bind/annotation/ControllerAdvice.html) annotated
class it detects. The ExceptionHandlerExceptionResolver
will retrieve these
from the context, and sort them using using
[AnnotationAwareOrderComparator
](http://docs.spring.io/spring/docs/current/javadoc-
api/org/springframework/core/annotation/AnnotationAwareOrderComparator.html)
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
](http://docs.spring.io/spring/docs/current/javadoc-
api/org/springframework/web/method/annotation/ExceptionHandlerMethodResolver.html)
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
](http://docs.spring.io/spring/docs/current/javadoc-
api/org/springframework/core/Ordered.html) or annotating it with
[@Order
](http://docs.spring.io/spring/docs/current/javadoc-
api/org/springframework/core/annotation/Order.html) or
@Priority
and giving it an appropriate value.