Spring MVC - How to handle errors from a controller?

In a web application, it is very important to handle errors. Proper error handling would protect the application from several vulnerabilities, including Security Misconfiguration. In this post, you will see how you can handle exceptions that arise out of a Spring MVC based application.

There are multiple ways to handle exceptions and we will cover them one by one. Let's first create a simple controller like so.

@RestController
public class MyController {

  private boolean throwError = true;

  @GetMapping("/myfirstrequest")
  private String myLocalRequestHandler() {

    if (throwError) {
      throw new MyLocalException("Local exception");
    }
    else {
      return "My Local Response!";
    }

  }

  @GetMapping("/mysecondrequest")
  private String mySecondRequestHandler() {

    if (throwError) {
      throw new MyGlobalException("Global exception");
    }
    else {
      return "My Second Response!";
    }

  }

  @GetMapping("/mythirdrequest")
  private String myThirdRequestHandler() {

    if (throwError) {
      throw new MyOtherException("Other exception");
    }
    else {
      return "My Third Response!";
    }

  }

}


There are three requests that this controller handles. GET /myfirstrequest, which throws out a MyLocalException,  GET /mysecondrequest, which throws out a MyGlobalException and GET /mythirdrequest, which throws out MyOtherException.

Assume that MyLocalException is thrown only by this controller and MyGlobalException and MyOtherException can be thrown by any controller in the application. Let's see how you can handle these exceptions.

@ExceptionHandler


ExceptionHandler annotation can be used to mark a controller method to handle exceptions e.g. in our controller, to handle MyLocalException, we can define an exception handler within the controller as follows.

  @ExceptionHandler({MyLocalException.class})
  @ResponseStatus(HttpStatus.I_AM_A_TEAPOT)
  private void exceptionHandler(Exception ex, HttpServletRequest request,
               HttpServletResponse response) throws IOException {

    log.error("Handling My Local Exception : ", ex);

  }

Now, when you invoke GET /myfirstrequest, the request handler would throw MyLocalException and framework would invoke the exceptionHandler method. The method accepts multiple optional parameters. The Exception parameter would hold the exception that is being handled. Apart from this, the method can also have parameters for http request, response, session objects, locale and the model itself.

If you want to set the HTTP status on the response, then annotate the handler with @ResponseStatus with the status code. The handler method can return ModelAndView, Model, View name or a ResponseBody itself. For a full list of parameter and return types, check the Javadoc for @ExceptionHandler.

@ControllerAdvice


ControllerAdvice can be used to handle controller exceptions at a global level. The handlers defined within the ControllerAdvice classes will be invoked for exceptions raised by any controller. ControllerAdvice can also be used to define common binding initializers and ModelAttribute binding. If you want to apply the Advice only on select controllers, then you can set the basePackages and annotations attributes to narrow it down. You can define a handler with ControllerAdvice as shown below.

@ControllerAdvice(basePackages = {"com.codelooru.web"})
public class ControllerExceptionHandler {

  @ExceptionHandler(MyGlobalException.class)
  @ResponseStatus(HttpStatus.I_AM_A_TEAPOT)
  public void handleGlobalException(Exception ex) {

    log.error("Handling My Global Exception : ", ex);

  }

}

The handler class above is marked with @ControllerAdvice with a basePackages filtering on "com.codelooru.web". This means it would be applicable only on the classes belonging to "com.codelooru.web" and its sub packages. You would also notice that we still use the same @ExceptionHandler annotation to define the exception handler methods. It behaves exactly the same, except that the scope is broad.

Now when you invoke GET /mysecondrequest, the request handler would throw MyGlobalException and would be handled by the handleGlobalException method.

HandlerExceptionResolver


HandlerExceptionResolver is an older way of handling global controller exceptions. It exists from the pre-annotations era. Exception handlers should implement the HandlerExceptionResolver interceptor and implement the resolveException method to handle the exceptions. The following is an example of a HandlerExceptionResolver.

@Component
public class MyHandlerExceptionResolver extends AbstractHandlerExceptionResolver {
  
  @Override
  protected ModelAndView doResolveException(HttpServletRequest request,
                  HttpServletResponse response, Object handler, Exception ex) {

    log.error("Handling My Other Exception : ", ex);
    return null;

  }
  
}

The class above would handle any exceptions that are thrown out of the controller and not handled by any other exception handler. doResolveException can update the ModelAndView to update the model and route to an error view.

Now, when you invoke GET /mythirdrequest, the controller would throw the MyOtherException and this would be caught and handled by the MyHandlerExceptionResolver.


Popular posts from this blog

How to Timeout JDBC Queries