Exception Handling for REST with Spring using ExceptionHandler and ControllerAdvice Annotation

Exception Handling for REST with Spring using ExceptionHandler and ControllerAdvice Annotation
Table of Contents

  1. Overview
  2. @ExceptionHandler
  3. @ControllerAdvice
  4. ExceptionHandlerResolver
  5. Handle the Access Denied in Spring Security
  6. Summary

1. Overview
Here we are going describe how to implement Exception Handling with Spring for a REST API.


Before Spring 3.2
there are two main approaches to handling exceptions in a Spring MVC application were: HandlerExceptionResolver or the @ExceptionHandler annotation. Both of these have some clear downsides.

After Spring 3.2
We now have the new @ControllerAdvice annotation to address the limitations of the previous two solutions.

All of these do have one thing in common – they deal with the separation of concerns very well. The app can throw exception normally to indicate a failure of some kind – exceptions which will then be handled separately.


2. The Controller level using @ExceptionHandler

Here we will define a method to handle exceptions, and annotate that with @ExceptionHandler at controller level. This solution is limited to the controller only for same type of exceptions i.e. this approach has a major drawback – the @ExceptionHandler annotated method is only active for that particular Controller, not globally for the entire application. Of course adding this to every controller makes it not well suited for a general exception handling mechanism.

We can avoid this limitation by making base controller which is extended by every controllers in the application however this can be a problem for applications where, for whatever reasons, the Controllers cannot be made to extend from such a class because this not good approach for reducing loose coupling.

@Controller
public class WebController {
       @ExceptionHandler(StudentNotFoundException.class)
 public ModelAndView handleStudentNotFoundException(StudentNotFoundException ex) {
  Map<String, String> model = new HashMap<String, String>();
  model.put("exception", ex.toString());
  return new ModelAndView("student.error", model);

 }
}

3. The New @ControllerAdvice (Spring 3.2 and Above)

From Spring 3.2 offers to global exception handling @ExceptionHandler with the new @ControllerAdvice annotation, this enables a mechanism that breaks away from the older MVC model and makes use of ResponseEntity along with the type safety and flexibility of @ExceptionHandler:

package com.doj.spring.web.controller;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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.mvc.method.annotation.ResponseEntityExceptionHandler;

@ControllerAdvice
public class RestResponseEntityExceptionHandler extends
  ResponseEntityExceptionHandler {
 @ExceptionHandler(value = { IllegalArgumentException.class, IllegalStateException.class })
    protected ResponseEntity<Object> handleConflict(RuntimeException ex, WebRequest request) {
        String bodyOfResponse = "This should be application specific";
        return handleExceptionInternal(ex, bodyOfResponse, 
          new HttpHeaders(), HttpStatus.CONFLICT, request);
    }
}


The new annotation allows the multiple scattered @ExceptionHandler from before to be consolidated into a single, global error handling component.

The actual mechanism is extremely simple but also very flexible:

  • it allows full control over the body of the response as well as the status code
  • it allows mapping of several exceptions to the same method, to be handled together
  • it makes good use of the newer RESTful ResposeEntity response


One thing to keep in mind here is to match the exceptions declared with @ExceptionHandler with the exception used as argument of the method. If these don’t match, the compiler will not complain – no reason it should, and Spring will not complain either.

4. The HandlerExceptionResolver

It will also allow us to implement a uniform exception handling mechanism in our REST API.

ExceptionHandlerExceptionResolver

This resolver was introduced in Spring 3.1 and is enabled by default in the DispatcherServlet. This is actually the core component of how the @ExceptionHandler mechanism presented earlier works.

DefaultHandlerExceptionResolver

This resolver was introduced in Spring 3.0 and is enabled by default in the DispatcherServlet. It is used to resolve standard Spring exceptions to their corresponding HTTP Status Codes.

ResponseStatusExceptionResolver

This resolver was also introduced in Spring 3.0 and is enabled by default in the DispatcherServlet. It’s main responsibility is to use the @ResponseStatus annotation available on custom exceptions and to map these exceptions to HTTP status codes.

package com.doj.spring.web.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="Student Not Found")
public class StudentNotFoundException extends RuntimeException{

 /**
  * 
  */
 private static final long serialVersionUID = -2581975292273282583L;
 
 String errorMessage;
 
 String errorCode;

 public StudentNotFoundException(String errorMessage, String errorCode) {
  super();
  this.errorMessage = errorMessage;
  this.errorCode = errorCode;
 }

 public String getErrorMessage() {
  return errorMessage;
 }

 public void setErrorMessage(String errorMessage) {
  this.errorMessage = errorMessage;
 }

 public String getErrorCode() {
  return errorCode;
 }

 public void setErrorCode(String errorCode) {
  this.errorCode = errorCode;
 }
 
}


Same as the DefaultHandlerExceptionResolver, this resolver is limited in the way it deals with the body of the response – it does map the Status Code on the response, but the body is still null.

Custom HandlerExceptionResolver
The combination of DefaultHandlerExceptionResolver and ResponseStatusExceptionResolver goes a long way towards providing a good error handling mechanism for a Spring RESTful Service. The downside is – as mentioned before – no control over the body of the response.

Ideally, we’d like to be able to output either JSON or XML, depending on what format the client has asked for via the Accept header.

5. Handle the Access Denied in Spring Security

MVC – Custom Error Page
XML configuration:
<http>
    <intercept-url pattern="/admin/*" access="hasAnyRole('ROLE_ADMIN')"/>   
    ... 
    <access-denied-handler error-page="/custom-error-page" />
</http>

Java Configuration:
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .antMatchers("/admin/*").hasAnyRole("ROLE_ADMIN")
        ...
        .and()
        .exceptionHandling().accessDeniedPage("/custom-error-page");
}

When users tries to access a resource without having enough authorities, they will be redirected to "/custom-error-page".

6. Summary

This tutorial discussed several ways to implement an exception handling mechanism for a REST API in Spring.



No comments:

Post a Comment