Spring MVC - Annotation based Exception handling using @ExceptionHandler

[Updated: Feb 8, 2017, Created: Jan 18, 2017]

The annotation @ExceptionHandler can be used on methods to return custom response content to the user.

Following are the important things to use this annotation:

  1. @ExceptionHandler is used on controller's methods which are not handler method i.e. which are not already annotated with @RequestMapping.

  2. This annotation has only one element. Here's the snippet of it:
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface ExceptionHandler {
    	Class<? extends Throwable>[] value() default {};
    }
    

    The 'value' element is for specifying exception type. If an exception is thrown, then annotated method will only be invoked with the matching 'value' exception type. The match is done based on the most specialized type, for example if:

    • method A has specified: @ExceptionHandler(RuntimeException.class)
    • and method B has specified: @ExceptionHandler(Exception.class)
    • a handler method throws RuntimeException, then A will be invoked.
    • if method A doesn't exists, then thrown RuntimeException will instead match method B, because Exception is the super class of RuntimeException.


  3. Methods annotated with this annotation are allowed to have very flexible signatures. They can also have the parameter of the thrown exceptions which trigger them. They can have Model parameter but it's always empty. Check out the complete list here.

  4. If @ExceptionHandler's 'value' element is empty, it will default to the exception specified in the method parameters. There should be at most one exception specified in the method parameter list.

  5. We can use @ResponseBody to return the error response straight from the method or alternatively we can return a view name.

  6. We can also optionally use @ResponseStatus along with @ExceptionHandler to specify response status code.

  7. The method annotated with @ExceptionHandler must be in the same @Controller class where exception is thrown.

  8. Mapping exactly same exception with multiple @ExceptionHandler methods will cause this exception
      java.lang.IllegalStateException: Ambiguous @ExceptionHandler method mapped for ........................

  9. In the background, ExceptionHandlerExceptionResolver (an implementation of HandlerExceptionResolver) is used for exception resolving and invoking the @ExceptionHandler methods for us. By default it's instance is configured with DispatcherServlet, so we don't have to do any related configuration to use it.

Example

Creating Controller

In the following controller, three handler methods throw different exceptions and three methods are defined with @ExceptionHandler. Please match thrown exception type to value of @ExceptionHandler to figure out what method will be invoked on exception scenarios.

@Controller
public class ExampleController {

    @RequestMapping("/data/{year}")
    @ResponseBody
    public String handleRequest (@PathVariable("year") int year) throws Exception {
        if (!Year.now().isAfter(Year.of(year))) {
            throw new Exception("Year is not before current year: " + year);
        }
        return "data response " + year;
    }

    @RequestMapping("/test/{id}")
    @ResponseBody
    public String handleRequest2 (@PathVariable("id") String id) {
        int i = Integer.parseInt(id);
        return "test response " + i;
    }

    @RequestMapping("/admin")
    @ResponseBody
    public String handleRequest2 (HttpServletRequest request)
              throws UserNotLoggedInException {

        Object user = request.getSession()
                             .getAttribute("user");
        if (user == null) {
            throw new UserNotLoggedInException("user: " + user);
        }
        return "test response " + user;
    }

    @ExceptionHandler(NumberFormatException.class)
    public String exceptionHandler (NumberFormatException re, Model model) {
        model.addAttribute("exception", re);
        return "errorPage";
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public String exceptionHandler2 (Exception re, Model model) {
        System.out.println(model);
        return "Exception: " + re.getMessage();
    }

    @ResponseStatus(HttpStatus.FORBIDDEN)
    @ExceptionHandler(UserNotLoggedInException.class)
    public String exceptionHandler3 (UserNotLoggedInException e, Model model) {
        model.addAttribute("exception", e);
        return "errorPage";
    }
}
public class UserNotLoggedInException extends Exception {

    public UserNotLoggedInException (String message) {
        super(message);
    }
}

src/main/webapp/WEB-INF/views/errorPage.jsp

<%@ page language="java"
    contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>

<html>
<body>
<h3>This is custom exception page</h3>
<p>Exception Type: <b>${exception['class'].simpleName}</b></p>
 <p>Exception Message: <b>${exception.message}</b></p>

</body>
</html>

Running with embedded tomcat server

mvn clean install tomcat7:run-war

Output

We are using HTTPie to test responses:

We are going to show related code snippets to figure out the result easily.

/data/{year}

@Controller
public class ExampleController {
    @RequestMapping("/data/{year}")
    @ResponseBody
    public String handleRequest (@PathVariable("year") int year) throws Exception {
        if (!Year.now().isAfter(Year.of(year))) {
            throw new Exception("Year is not before current year: " + year);
        }
        return "data response " + year;
    }
    ......
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public String exceptionHandler2 (Exception re, Model model) {
        System.out.println(model);
        return "Exception: " + re.getMessage();
    }
    .....
}

/test/{id}

@Controller
public class ExampleController {
    .......
    @RequestMapping("/test/{id}")
    @ResponseBody
    public String handleRequest2 (@PathVariable("id") String id) {
        int i = Integer.parseInt(id);
        return "test response " + i;
    }
    ........
    @ExceptionHandler(NumberFormatException.class)
    public String exceptionHandler (NumberFormatException re, Model model) {
        model.addAttribute("exception", re);
        return "errorPage";
    }
    ..........
}

/admin

@Controller
public class ExampleController {
   .....
   @RequestMapping("/admin")
    @ResponseBody
    public String handleRequest2 (HttpServletRequest request)
              throws UserNotLoggedInException {
        Object user = request.getSession()
                             .getAttribute("user");
        if (user == null) {
            throw new UserNotLoggedInException("user: " + user);
        }
        return "test response " + user;
    }
    ......
    @ResponseStatus(HttpStatus.FORBIDDEN)
    @ExceptionHandler(UserNotLoggedInException.class)
    public String exceptionHandler3 (UserNotLoggedInException e, Model model) {
        model.addAttribute("exception", e);
        return "errorPage";
    }
}




Example Project

Dependencies and Technologies Used :

  • spring-webmvc 4.3.5.RELEASE: Spring Web MVC.
  • javax.servlet-api 3.0.1 Java Servlet API
  • JDK 1.8
  • Maven 3.3.9

Exception Handler Annotation Example Select All Download
  • exception-handler-annotation
    • src
      • main
        • java
          • com
            • logicbig
              • example
        • webapp
          • WEB-INF
            • views

See Also