Setting Precedence of Multiple @ControllerAdvice @ExceptionHandlers

ghz 1years ago ⋅ 718 views

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

  1. ExceptionHandlerExceptionResolver
  2. ResponseStatusExceptionResolver
  3. 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's Ordered 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.