What is @ControllerAdvice?

@ControllerAdvice annotation is used to define some attributes for many Spring controllers (@Controller) at a time.

For example, it can be used for a centralized exception control (with @ExceptionHandler annotation). This post will concentrate on this use.

Global and specific advices

If a @ControllerAdvice does not have any selectors specified via annotation attritubes, it defines a global advice which affects all the controllers, and its @ExceptionHandler will catch all the exceptions thrown by handle methods (and not just these exceptions, see below).

In Spring 4.0, the ability to define specific advices was added. @ControllerAdvice now has some attributes with which we can define advice selectors. These selectors define the scope of the advice, i.e. the exact set of controllers which will be adviced by it.

Global @ExceptionHandler catches ‘no man’s’ exceptions

‘No man’s’ exceptions are exceptions which occur before the handler to process the request is obtained.

So, if we don’t specify any attributes at @ControllerAdvice annotation, its @ExceptionHandler method will catch even HttpRequestMethodNotSupportedException when someone tries to issue a GET request to our POST-only controller.

But if we define a class, annotation or basePackage for an existing package, then the advice will not be global anymore and will not catch ‘no man’s’ exceptions.

basePackages does not work for controllers proxied with Proxy

To have the ability to intercept controller invocations (for example, to handle exceptions), our advice has to wrap controller instance in a proxy. Proxy creation options:

  1. If the controller class has at least one implemented interface, an interface-based proxy is created using Proxy class.
  2. If the controller class does not implement any inferfaces, then CGLIB is used; proxy class is created at runtime; this class extends our initial controller class.
  3. For CGLIB option, Spring’s ControllerAdviceBean determines correctly the controller class package: it just takes the superclass of CGLIB-generated class (this will be the real controller class as written by us) and then takes its package. In this case, basePackages of @ControllerAdvice works correctly.

But for the interface-proxy based option Spring has no ability to determine the real class of an instance wrapped with Proxy, so it tries to take the package of the Proxy instance. But proxy.getClass().getPackage() returns null! In Spring 4.0.5 this even causes a NullPointerException. In 4.0.9 the NPE was fixed, but the package will not be determined correctly, so basePackages will not work.

To sum up:

  1. If the controller class has at least one implemented interface, an interface-based proxy is created using Proxy class, and basePackages of @ControllerAdvice DOES NOT work.
  2. If the controller class does not implement any inferfaces, then CGLIB is used and basePackages attribute works.

What can we do?

Use annotations attribute. First, let’s create an annotation like

@ControllerAdvicedByMyAdvice

Then we annotate with this annotation all the controllers to which we want to apply the advice. And then we annotate the advice:

@ControllerAdvice(annotations = {ControllerAdvicedByMyAdvice.class})

This approach seems more reliable than the utilization of the basePackages attribute.