Spring @ControllerAdvice and @ExceptionHandler
Exception handling is a very essential feature of any Java application. Every good open-source framework allows writing the exception handlers in such a way that we can separate them from our application code. Well, Spring framework also allows us to do so using annotation @ExceptionHandler.
The @ExceptionHandler annotation is used for handling exceptions in specific handler classes and/or handler methods.
1. Spring @ExceptionHandler
To handle exceptions in Spring MVC, we can define a method in @Controller class and use the annotation @ExceptionHandler on it. Spring configuration will detect this annotation and register the method as an exception handler for the argument exception class and its subclasses.
@Controller public class PageController < @RequestMapping(value = "/", method = RequestMethod.GET) public ModelAndView getPage(@PathVariable("id") String id) throws Exception < Page page = . ; if(page == null) < throw new RecordNotException("Page not found for id : " + id); >return new ModelAndView("index"); > @ExceptionHandler(NullPointerException.class) public ModelAndView handleException(NullPointerException ex) < ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName("error"); modelAndView.addObject("message", ex.getMessage()); return modelAndView; >@ExceptionHandler(RecordNotException.class) public ModelAndView handleException(RecordNotException ex) < . return modelAndView; >>
Now every time, the controller encounter NullPointerException in request processing for any web request in this controller, control will automatically come to this handler method.
Handler methods that are annotated with this annotation are allowed to have very flexible signatures. They can accept arguments of different types. For example, an exception argument, request and/or response objects, session object, locale object and model object etc.
@ExceptionHandler(NullPointerException.class) public ModelAndView handleException(NullPointerException ex, HttpServletRequest request, Model model)
Similar to arguments, return types can be of different types. For example, ModelAndView object, Model object, View object, view name as String etc. We can mark the method to void also if the method handles the response itself by writing the response content directly to HttpServletResponse .
We may combine the ExceptionHandler annotation with @ResponseStatus for a specific HTTP error status.
@ExceptionHandler(AjaxException.class) @ResponseBody public ErrorResponse handleException(AjaxException ex, HttpServletResponse response)
2. Handling Multiple Exceptions
As mentioned earlier, the above exception handler will handle all exceptions, either instance of a given class or sub-classes of argument exceptions. But, if we want to configure @ExceptionHandler for multiple exceptions of different types, then we can specify all such exceptions in form of an array.
@ExceptionHandler() public ModelAndView handleException(Exception ex)
3. Global @ExceptionHandler with @ControllerAdvice
If we want to centralize the exception-handling logic to one class that is capable of handling exceptions thrown from any handler class/ controller class – then we can use @ControllerAdvice annotation.
By default, the methods in an @ControllerAdvice apply globally to all controllers. We can create a class and add @ControllerAdvice annotation on top. Then add @ExceptionHandler methods for each type of specific exception class in it.
Notice we extended the exception handler class with ResponseEntityExceptionHandler. It is a convenient base class for @ControllerAdvice classes that wish to provide centralized exception handling across all @RequestMapping methods through @ExceptionHandler methods.
@ControllerAdvice public class CustomExceptionHandler extends ResponseEntityExceptionHandler < @ExceptionHandler(Exception.class) public final ResponseEntityhandleAllExceptions(Exception ex, WebRequest request) < Listdetails = new ArrayList<>(); details.add(ex.getLocalizedMessage()); ErrorResponse error = new ErrorResponse(ApplicationConstants.SERVER_ERROR, details); return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR); > @ExceptionHandler(RecordNotFoundException.class) public final ResponseEntity handleUserNotFoundException(RecordNotFoundException ex, WebRequest request) < Listdetails = new ArrayList<>(); details.add(ex.getLocalizedMessage()); ErrorResponse error = new ErrorResponse(ApplicationConstants.RECORD_NOT_FOUND, details); return new ResponseEntity<>(error, HttpStatus.NOT_FOUND); > >
Where supporting classes are. This is for example only. Please create your application-specific exception and handle them.
@ResponseStatus(HttpStatus.NOT_FOUND) public class RecordNotFoundException extends RuntimeException < private static final long serialVersionUID = 1L; public RecordNotFoundException(String exception) < super(exception); >>
public class ErrorResponse < private String message; private Listdetails; >
For the demo, the below handler method is intentionally returning NullPointerException .
@RequestMapping(value="/demo/not-exist", method = RequestMethod.GET, headers="Accept=*/*") public @ResponseBody ModelAndView oneFaultyMethod() < if(true) < throw new NullPointerException("This error message if for demo only."); >return null; >
If we deploy the above application and hit the URL [/SpringApplication/users/demo/not-exist] in the browser, it will show the “error” page as configured in the first section.
< %@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> < %@ taglib prefix="x" uri="http://java.sun.com/jstl/xml" %> < %@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt" %> < %@ taglib prefix="sql" uri="http://java.sun.com/jstl/sql" %>This is sample error page :
Below will be the output in the browser.
Annotation Interface ExceptionHandler
- An exception argument: declared as a general Exception or as a more specific exception. This also serves as a mapping hint if the annotation itself does not narrow the exception types through its value() . You may refer to a top-level exception being propagated or to a nested cause within a wrapper exception. As of 5.3, any cause level is being exposed, whereas previously only an immediate cause was considered.
- Request and/or response objects (typically from the Servlet API). You may choose any specific request/response type, e.g. ServletRequest / HttpServletRequest .
- Session object: typically HttpSession . An argument of this type will enforce the presence of a corresponding session. As a consequence, such an argument will never be null . Note that session access may not be thread-safe, in particular in a Servlet environment: Consider switching the «synchronizeOnSession» flag to «true» if multiple requests are allowed to access a session concurrently.
- WebRequest or NativeWebRequest . Allows for generic request parameter access as well as request/session attribute access, without ties to the native Servlet API.
- Locale for the current request locale (determined by the most specific locale resolver available, i.e. the configured LocaleResolver in a Servlet environment).
- InputStream / Reader for access to the request’s content. This will be the raw InputStream/Reader as exposed by the Servlet API.
- OutputStream / Writer for generating the response’s content. This will be the raw OutputStream/Writer as exposed by the Servlet API.
- Model as an alternative to returning a model map from the handler method. Note that the provided model is not pre-populated with regular model attributes and therefore always empty, as a convenience for preparing the model for an exception-specific view.
- A ModelAndView object (from Servlet MVC).
- A Model object, with the view name implicitly determined through a RequestToViewNameTranslator .
- A Map object for exposing a model, with the view name implicitly determined through a RequestToViewNameTranslator .
- A View object.
- A String value which is interpreted as view name.
- @ResponseBody annotated methods (Servlet-only) to set the response content. The return value will be converted to the response stream using message converters.
- An HttpEntity or ResponseEntity object (Servlet-only) to set response headers and content. The ResponseEntity body will be converted and written to the response stream using message converters.
- void if the method handles the response itself (by writing the response content directly, declaring an argument of type ServletResponse / HttpServletResponse for that purpose) or if the view name is supposed to be implicitly determined through a RequestToViewNameTranslator (not declaring a response argument in the handler method signature).