Centralize validation and exception handling with @ControllerAdvice

1 Introduction

The ControllerAdvice annotation introduced by Spring 3.2 allows us to handle several functionalities in a way that can be shared by all controllers (through its handler methods, annotated with @RequestMapping). This annotation is mainly used to define the following methods:
  • @ExceptionHandler: Handles exceptions thrown by handler methods.
  • @InitBinder: Initializes the WebDataBinder, which will be used to populate objects passed as arguments to the handler methods. Usually, it is used to register property editors or validators.
  • @ModelAttribute: Binds a parameter or return value to an attribute, which will then be exposed to a web view.
Source code can be found at github.

2 Adding validation and exception handling

The following is a description of  the controller’s handler methods before implementing the @ControllerAdvice.
Add person controller:


 

Besides the handler method, this controller has the following methods:
  • initBinder: Registers a validator to prevent that a person with invalid data is introduced. To make the validator validate the person object passed as a parameter, it is necessary to add the @Valid annotation to the argument. Spring 3 fully supports JSR-303 bean validation API, but it does not implement it. The reference implementation which is used in this example is Hibernate Validator 4.x.
  • handleValidationException: Handles the MethodArgumentNotValidException that can be thrown by the handler method. This exception is thrown by Spring MVC when an argument annotated with @Valid, fails its validation.

Get person controller:

 

This controller adds an exception handler for handling when a request asks to retrieve a person that does not exist.



Update person controller:

 

We are repeating code, since @ExceptionHandler is not global.

3 Centralizing code

ControllerAdvice annotation is itself annotated with @Component. Hence, the class that we are implementing will be autodetected through classpath scanning.


Finally, we can delete these methods from the controllers, taking rid of code duplication, since this class will handle exception handling and validation for all handler methods annotated with @RequestMapping.

 

4 Testing

The methods described below, test the retrieval of persons:


 

The rest of tests can be found with the source code linked above.

 

Share it:

13 thoughts on “Centralize validation and exception handling with @ControllerAdvice

  1. When I put the @ExceptionHandler in the controller class, it works fine but when I am removing it and putting in the class annotated with @ControllerAdvice..it does not reach in this exception handler method. Do I have to do anything specific to load Controller Advice class other than component scan config which I already have.

  2. You don't need to do anything else. Are you sure that the package of the @ControllerAdvice annotated class is located within the component scan path? Also check if the exception captured by the exception handler is the same as the one being thrown by the @RequestMapping method.

  3. Prou interessant seria fer ús de validation groups, validació de camps més complexos (amb propis validadors) juntament amb anotacions de validació 🙂

  4. Hi Manish,

    The purpose of PersonValidator is to prevent an invalid person to be introduced. Check initBinder description in section 2 of this post. The class is not shown here because the validation implementation is not the central point of this post. However, if you want to check it, you can take a look at the source code provided at the beginning. Here's a direct link to the requested validator class:
    https://github.com/xpadro/spring-rest/tree/master/rest-controlleradvice/src/main/java/xpadro/spring/rest/validator

  5. Nice and clean example. It seems with SpringMVC there are many different ways to achieve the same result. What is your opinion on returning a ResponseEntity from your getPerson method instead of @ResponseBody Person ?

  6. Hi Andre,

    Thanks for your feedback, I appreciate it. About your question, I'm returning a @ResponseBody because I just want access to the response body. If you see the test example at section 4, returning @ResponseBody plus using message converters allows us to receive a Person instance directly in our test class.

    I would use @ResponseEntity if I wanted more control and needed access to response headers in addition to the body. Check the following example:

    ResponseEntity entity = template.getForEntity("http://example.com", String.class);
    String body = entity.getBody();
    MediaType contentType = entity.getHeaders().getContentType();
    HttpStatus statusCode = entity.getStatusCode();

    The above example is taken from http://docs.spring.io/spring/docs/3.0.x/api/org/springframework/http/ResponseEntity.html

  7. Lovely. Ive been wondering how to setup all my property editors in a single place. Your combination of @ControllerAdvice and @InitBinder did the trick

Leave a Reply

Your email address will not be published. Required fields are marked *