Exception handling is an integral part of any application. It involves catching errors and showing a graceful error message to the end user.
Normally, try-catch blocks are used in a java application to handle exceptions where the error raised can be shown on a console or UI.

But, in a web application, the error has to be sent to the browser in the form of a proper response.
This means that there should be a mechanism where any error raised from any part of the application is sent to the browser.

Spring Exception Handling Mechanism
Spring MVC provides an easy and centralized solution for exception handling in MVC applications.
Using Spring’s exception handling mechanism, developers can write the exception handling code at one single location so that exception raised from any part of the application will arrive at this central location.

There are a couple of ways to achieve this :

  1. Specify an exception handler method for a class whose methods can throw an exception.
    Any uncaught exception thrown by the methods of this class will be directed to this exception handler.
    The exception handler will only work for the methods of the class in which the handler method is written.
    It is achieved by using @ExceptionHandler annotation over the exception handler method.
  2. Specify a global exception handler class containing exception handling methods so that any uncaught exception thrown from the entire application will at last arrive at one of the exception handler methods before the response is sent to the browser.
    This is achieved using @ExceptionHandler and @ControllerAdvice annotations.

Using @ExceptionHandler
This annotation is applied over a method which acts as an exception handler.
This annotation takes an exception class as argument so that the method knows which types of exceptions it should handle.
Example,

@ExceptionHandler(IOException.class)
public void exceptionHandler() { 

}

In the above piece of code, an exception handler method exceptionHandler is declared which is annotated with @ExceptionHandler with java.io.IOException as value.
This means that the method exceptionHandler will catch any uncaught exceptions of type java.io.IOException from the class in which this method is declared.

Spring provides much flexibility in declaring exception handler methods annotated with @ExceptionHandler annotation.

This method can :

  1. Have any return type
    The return type can be void, a String, ModelAndView object, java.lang.Map or a custom JSON object depending on the requirement.Thus all the below signatures are valid.

    @ExceptionHandler(IOException.class)
    public void exceptionHandler(){
    }
    
    @ExceptionHandler(IOException.class)
    public String exceptionHandler(){
        return "error";
    }
    @ExceptionHandler(IOException.class)
    public ModelAndView exceptionHandler(){
        ModelAndView errorView = new ModelAndView("errorPage");
        return errorView;
    }
    
    @ExceptionHandler(IOException.class)
    public Map<String, String> exceptionHandler(){
        Map<String, String> errorMap = new HashMap<String, String>();
        return errorMap;
    }

    In case of exception handler method returning a String, the controller will return error.jsp(depending on the view resolver configured).
    In case of return type as ModelAndView, errorPage.jsp will be returned and so on.

  2. Flexible argument types
    It can accept no arguments, argument representing the exception raised and the HttpServletRequest object as shown below.
    When the exception is provided as argument, it will contain the actual exception object which is thrown from the application.

    @ExceptionHandler(IOException.class)
    public void exceptionHandler(){
    }
    
    @ExceptionHandler(IOException.class)
    public String exceptionHandler(IOException error){
        return "error";
    }
    @ExceptionHandler(IOException.class)
    public ModelAndView exceptionHandler(HttpServletRequest request, 
                            IOException error){
       ModelAndView errorView = new ModelAndView("errorPage");
       errorView.addObject("errorObj", error);
       errorView.addObject("url", request.getRequestURL());
       return errorView;
    }
    
    @ExceptionHandler(IOException.class)
    public Map<String, String> exceptionHandler(IOException error){
        Map<String, String> errorMap = new HashMap<String, String>();
        errorMap.put("errorMesg", error.getMessage());
        return errorMap;
    }

    If you want that the exception object is available on the jsp(or the view), return a ModelAndView object and add the error object to this ModelAndView as shown in the third example.
    In this case, the exception object will be available on the view bound by the name “errorObj”.

Example

Below is an example of an exception handler written in a controller class.
This example uses a custom exception which is created as follows.

package com.codippa;

public class AppException extends Exception {

 private String error;

 public AppException (String message) {
    super(message);
    error = message;
 }

 public String getError() {
    return error;
 }
}

Controller class is written as

package com.codippa;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class TestController {
/**
 * Method to serve the requested URL
 * @return
 */
 @RequestMapping("/home")
 public ModelAndView showHomePage() {
     ModelAndView homePage = new ModelAndView("home");
     String s = "throw";
     //voluntarily throw exception
     if(s.equals("throw")){
         throw new AppException ("Exception example");
     }
     return homePage;
 }
 
/**
 * Exception handler method for AppException
 *
 * @return
 * @throws
 */
 @ExceptionHandler(AppException.class)
 public ModalAndView exceptionHandler(AppException e){
    ModelAndView homePage = new ModelAndView();
    homePage.setViewName("error");
    homePage.addObject("error", e);
    return homePage;
 }
}

In the above example, when the controller method throws AppException, it is caught by the method exceptionHandler written in the same class since it is annotated with @ExceptionHandler configured to handle exceptions of type AppException.
Since the return type of the method is a ModelAndView with view set to “error”, the response will be error.jsp.

Exception object is set in the ModelAndView object with the name “error”, hence it can be accessed in the jsp using this name.
Contents of error.jsp could be written as

 <html>
<body>
Error : ${error.message}
</body>
</html>

where message is a field in java.lang.Exception.

Global Exception Handler : @ControllerAdvice

The above approach is suitable when the exception handler method is present in the same class from which the exception is thrown.

In scenarios where you want a centralized exception handling mechanism so that exceptions thrown across the application are handled at one place, there is another method.
This method uses @ControllerAdvice annotation along with @ExceptionHandler.
In this method, a separate class containing exception handler is created.
This class is annotated with @ControllerAdvice and contains exception handler methods which are annotated with @ExceptionHandler annotation.

A single class can contain multiple exception handler methods for handling different types of exceptions.

Example

package com.codippa;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;

@ControllerAdvice
public class GlobalErrorHandler {
 
   /**
    * Method called when IOException is thrown from the application
    * 
    * @param e
    * @return
    */
   @ExceptionHandler(IOException.class)
   public ModelAndView inputOutputError(Exception e) {
      ModelAndView error = new ModelAndView("error");
      error.addObject("errorObj", e);
      error.addObject("errorMesg", "IO Error");
      return error;
   }

   /**
    * Method called when AppException is thrown from the application
    * 
    * @param e
    * @return
    */
    @ExceptionHandler(AppException.class)
    public ModelAndView applicationError(AppException e) {
       ModelAndView error = new ModelAndView("error");
       error.addObject("errorObj", e);
       error.addMessage("errorMesg", "Application Error");
       return error;
    }
}

In the above example, a GlobalErrorHandler class is declared.
This class is annotated with @ControllerAdvice annotation and it contains multiple methods annotated with @ExceptionHandler with different exception types which act as error handlers.

This class acts as a helper to all the application controllers.
Thus, whenever any uncaught exception is thrown from any controller, it arrives to the appropriate error handler methods in this class.
Both the methods of this class return error.jsp which displays the error message sent from both the methods.

Contents of error.jsp are

<html>
<body>
Error : ${errorMesg}
</body>
</html>

Let’s tweak in

  1. A class annotated with @ControllerAdvice acts as a supporter to application controllers and catches only those uncaught exceptions which are thrown from controller classes.
  2. Polymorphism does not work in case of exception types handled by the exception handlers.
    That is, suppose a controller method throws an exception and there is no matching exception handler method for this exception but there is an exception handler for its super class, then this exception will NOT be caught by the exception handler.
    Example,
    let’s say a controller method throws a java.io.FileNotFoundException and there is an exception handler method for java.io.IOException, then this exception will not be caught by this handler.
  3. When returning a String from a controller method, you need to apply @ResponseBody annotation before the method otherwise, Spring will search for a view matching the name of this String.

Hope the article was useful.

Leave a Reply