About Rafal Borowiec

Rafal is an IT specialist with about 8 years of commercial experience, specializing in software testing and quality assurance, software development, project management and team leadership.

@ControllerAdvice improvements in Spring 4

Among many new features in Spring 4 I found @ControllerAdvice improvements. @ControllerAdvice is a specialization of a @Component that is used to define @ExceptionHandler, @InitBinder, and @ModelAttribute methods that apply to all @RequestMapping methods. Prior to Spring 4, @ControllerAdvice assisted all controllers in the same Dispatcher Servlet. With Spring 4 it has changed. As of Spring 4 @ControllerAdvice may be configured to support defined subset of controllers, whereas the default behavior can be still utilized.

@ControllerAdvice assisting all controllers

Let’s assume we want to create an error handler that will print application errors to the user. Let’s assume this is a basic Spring MVC application with Thymeleaf as a view engine and we have an ArticleController with the following @RequestMapping method:

package pl.codeleak.t.articles;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("article")
class ArticleController {

    @RequestMapping("{articleId}")
    String getArticle(@PathVariable Long articleId) {
        throw new IllegalArgumentException("Getting article problem.");
    }
}

Our method throws an imaginary exception, as we can see. Let’s now create an exception handler using @ControllerAdvice. (this is not only possible method in Spring to deal with exceptions).

package pl.codeleak.t.support.web.error;

import com.google.common.base.Throwables;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.ModelAndView;

@ControllerAdvice
class ExceptionHandlerAdvice {

 @ExceptionHandler(value = Exception.class)
 public ModelAndView exception(Exception exception, WebRequest request) {
  ModelAndView modelAndView = new ModelAndView("error/general");
  modelAndView.addObject("errorMessage", Throwables.getRootCause(exception));
  return modelAndView;
 }
}

The class is not public, as it does not to be. We added @ExceptionHandler method that will handle all types of Exceptions and it will return the “error/general” view:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Error page</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <link href="../../../resources/css/bootstrap.min.css" rel="stylesheet" media="screen" th:href="@{/resources/css/bootstrap.min.css}"/>
    <link href="../../../resources/css/core.css" rel="stylesheet" media="screen" th:href="@{/resources/css/core.css}"/>
</head>
<body>
<div class="container" th:fragment="content">
    <div th:replace="fragments/alert :: alert (type='danger', message=${errorMessage})"> </div>
</div>
</body>
</html>

To test the solution we can either run the server or (preferably) create a test with Spring MVC Test module. Thanks to the fact that we use Thymeleaf, we can verify the rendered view:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = {RootConfig.class, WebMvcConfig.class})
@ActiveProfiles("test")
public class ErrorHandlingIntegrationTest {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Before
    public void before() {
        this.mockMvc = webAppContextSetup(this.wac).build();
    }

    @Test
    public void shouldReturnErrorView() throws Exception {
        mockMvc.perform(get("/article/1"))
                .andDo(print())
                .andExpect(content().contentType("text/html;charset=ISO-8859-1"))
                .andExpect(content().string(containsString("java.lang.IllegalArgumentException: Getting article problem.")));
    }
}

We expect the content type is text/html and the view contains the HTML fragment with an error message. Not really user friendly, though. But the test is green.

Using the above solution we provide a general mechanism for handling errors of all our controllers. As mentioned earlier, we can do much more with @ControllerAdvice:. E.g:

@ControllerAdvice
class Advice {

    @ModelAttribute
    public void addAttributes(Model model) {
        model.addAttribute("attr1", "value1");
        model.addAttribute("attr2", "value2");
    }

    @InitBinder
    public void initBinder(WebDataBinder webDataBinder) {
        webDataBinder.setBindEmptyMultipartFiles(false);
    }
}

@ControllerAdvice assisting selected subset of controllers

As of Spring 4, @ControllerAdvice can be customized through annotations(), basePackageClasses(), basePackages() methods to select a subset of controllers to assist. I will demonstrate a simple case how to utilize this new feature.

Let’s assume we want to add an API to expose articles via JSON. So we can define a new controller like this:

@Controller
@RequestMapping("/api/article")
class ArticleApiController {

    @RequestMapping(value = "{articleId}", produces = "application/json")
    @ResponseStatus(value = HttpStatus.OK)
    @ResponseBody
    Article getArticle(@PathVariable Long articleId) {
        throw new IllegalArgumentException("[API] Getting article problem.");
    }
}

Our controller is not very sophisticated. It returns an Article as a response body, as @ResponseBody annotation indicates. Of course, we want to deal with exceptions. And we don’t want to return an error as text/html but as application/json. Let’s create a test then:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = {RootConfig.class, WebMvcConfig.class})
@ActiveProfiles("test")
public class ErrorHandlingIntegrationTest {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Before
    public void before() {
        this.mockMvc = webAppContextSetup(this.wac).build();
    }

    @Test
    public void shouldReturnErrorJson() throws Exception {
        mockMvc.perform(get("/api/article/1"))
                .andDo(print())
                .andExpect(status().isInternalServerError())
                .andExpect(content().contentType("application/json"))
                .andExpect(content().string(containsString("{\"errorMessage\":\"[API] Getting article problem.\"}")));
    }
}

The test is red. What we can do to make it green? We need to make another advice, this time targeting only our Api controller. For that, we will use @ControllerAdvice annotations() selector. In order to do it we need to either create a customer or use existing annotation. We will use @RestController predefined annotation. Controllers annotated with @RestController assume @ResponseBody semantic by default. We may slighlty modify our controller by replacing @Controller with @RestController and removing @ResponseBody from the handler’s method:

@RestController
@RequestMapping("/api/article")
class ArticleApiController {

    @RequestMapping(value = "{articleId}", produces = "application/json")
    @ResponseStatus(value = HttpStatus.OK)
    Article getArticle(@PathVariable Long articleId) {
        throw new IllegalArgumentException("[API] Getting article problem.");
    }
}

We also need to create another advice that will return ApiError (simple POJO):

@ControllerAdvice(annotations = RestController.class)
class ApiExceptionHandlerAdvice {

    /**
     * Handle exceptions thrown by handlers.
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public ApiError exception(Exception exception, WebRequest request) {
        return new ApiError(Throwables.getRootCause(exception).getMessage());
    }
}

This time when we run our test suite, both tests are green meaning that ExceptionHandlerAdvice assisted “standard” ArticleController whereas ApiExceptionHandlerAdvice assisted ArticleApiController.

Summary

In the above scenario I demonstrated how easily we can utilize new configuration capabilities of @ControllerAdvice annotation and I hope you like the change as I do.

References

 

Reference: @ControllerAdvice improvements in Spring 4 from our JCG partner Rafal Borowiec at the Codeleak.pl blog.

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

JPA Mini Book

Learn how to leverage the power of JPA in order to create robust and flexible Java applications. With this Mini Book, you will get introduced to JPA and smoothly transition to more advanced concepts.

JVM Troubleshooting Guide

The Java virtual machine is really the foundation of any Java EE platform. Learn how to master it with this advanced guide!

Given email address is already subscribed, thank you!
Oops. Something went wrong. Please try again later.
Please provide a valid email address.
Thank you, your sign-up request was successful! Please check your e-mail inbox.
Please complete the CAPTCHA.
Please fill in the required fields.

One Response to "@ControllerAdvice improvements in Spring 4"

  1. Bot01 says:

    Thanks for the useful article.

Leave a Reply


six + 3 =



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.
Do you want to know how to develop your skillset and become a ...
Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

Get ready to Rock!
You can download the complementary eBooks using the links below:
Close