July 30, 2016

Error Handling for REST with Spring

Table of Contents
1. Overview
This article will illustrate how to implement Exception Handling with Spring for a REST API. We’ll look at both the recommended solution with Spring 3.2 and 4.x but also at the older options as well.
Before Spring 3.2, the 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 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. Solution 1 – The Controller level @ExceptionHandler
The first solution works at the @Controller level – we will define a method to handle exceptions, and annotate that with @ExceptionHandler:
1
2
3
4
5
6
7
public class FooController{
    ...
    @ExceptionHandler({ CustomException1.class, CustomException2.class })
    public void handleException() {
        //
    }
}
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.
The limitation is often worked around by having all Controllers extend a Base Controller class– however, this can be a problem for applications where, for whatever reasons, the Controllers cannot be made to extend from such a class. For example, the Controllers may already extend from another base class which may be in another jar or not directly modifiable, or may themselves not be directly modifiable.
Next, we’ll look at another way to solve the exception handling problem – one that is global and does not include any changes to existing artifacts such as Controllers.
3. Solution 2 – The HandlerExceptionResolver
The second solution is to define an HandlerExceptionResolver – this will resolve any exception thrown by the application. It will also allow us to implement a uniform exception handling mechanism in our REST API.
Before going for a custom resolver, let’s go over the existing implementations.
3.1. 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.
3.2. 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, namely Client error – 4xx and Server error – 5xx status codes. Here is the full list of the Spring Exceptions it handles, and how these are mapped to status codes.
While it does set the Status Code of the Response properly, one limitation is that it doesn’t set anything to the body of the Response. And for a REST API – the Status Code is really not enough information to present to the Client – the response has to have a body as well, to allow the application to give additional information about the failure.
This can be solved by configuring View resolution and rendering error content throughModelAndView, but the solution is clearly not optimal – which is why a better option has been made available with Spring 3.2 – we’ll talk about that in the latter part of this article.
3.3. ResponseStatusExceptionResolver
This resolver was also introduced in Spring 3.0 and is enabled by default in theDispatcherServlet. Its main responsibility is to use the @ResponseStatus annotation available on custom exceptions and to map these exceptions to HTTP status codes.
Such a custom exception may look like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public final class ResourceNotFoundException extends RuntimeException {
    public ResourceNotFoundException() {
        super();
    }
    public ResourceNotFoundException(String message, Throwable cause) {
        super(message, cause);
    }
    public ResourceNotFoundException(String message) {
        super(message);
    }
    public ResourceNotFoundException(Throwable cause) {
        super(cause);
    }
}
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.
3.4. SimpleMappingExceptionResolver and AnnotationMethodHandlerExceptionResolver
The SimpleMappingExceptionResolver has been around for quite some time – it comes out of the older Spring MVC model and is not very relevant for a REST Service. It is used to map exception class names to view names.
The AnnotationMethodHandlerExceptionResolver was introduced in Spring 3.0 to handle exceptions through the @ExceptionHandler annotation, but has been deprecated byExceptionHandlerExceptionResolver as of Spring 3.2.
3.5. Custom HandlerExceptionResolver
The combination of DefaultHandlerExceptionResolver and ResponseStatusExceptionResolvergoes 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).
This alone justifies creating a new, custom exception resolver:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Component
public class RestResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver {

    @Override
    protected ModelAndView doResolveException
      (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        try {
            if (ex instanceof IllegalArgumentException) {
                return handleIllegalArgument((IllegalArgumentException) ex, response, handler);
            }
            ...
        } catch (Exception handlerException) {
            logger.warn("Handling of [" + ex.getClass().getName() + "]
              resulted in Exception", handlerException);
        }
        return null;
    }

    private ModelAndView handleIllegalArgument
      (IllegalArgumentException ex, HttpServletResponse response) throws IOException {
        response.sendError(HttpServletResponse.SC_CONFLICT);
        String accept = request.getHeader(HttpHeaders.ACCEPT);
        ...
        return new ModelAndView();
    }
}
One detail to notice here is the Request itself is available, so the application can consider the value of the Accept header sent by the client. For example, if the client asks for application/jsonthen, in case of an error condition, the application should still return a response body encoded with application/json.
The other important implementation detail is that a ModelAndView is returned – this is the body of the response and it will allow the application to set whatever is necessary on it.
This approach is a consistent and easily configurable mechanism for the error handling of a Spring REST Service. It is does however have limitations: it’s interacting with the low levelHtttpServletResponse and it fits into the old MVC model which uses ModelAndView – so there’s still room for improvement.
4. New Solution 3 – The New @ControllerAdvice (Spring 3.2 And Above)
Spring 3.2 brings support for a global @ExceptionHandler with the new @ControllerAdviceannotation. 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:
1
2
3
4
5
6
7
8
9
10
@ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(value = { IllegalArgumentException.class, IllegalStateException.class })
    protected ResponseEntity 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 @ExceptionHandlerwith 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.
However, when the exception is actually thrown at runtime, the exception resolving mechanism will fail with:
1
2
java.lang.IllegalStateException: No suitable resolver for argument [0] [type=...]
HandlerMethod details: ...
5. Handle the Access Denied in Spring Security
The Access Denied occurs when an authenticated user tries to access resources that he doesn’t have enough authorities to access.
5.1. MVC – Custom Error Page
First, let’s look at the MVC style of solution and see how to customize an error page for Access Denied:
The XML configuration:
1
2
3
4
5
     pattern="/admin/*" access="hasAnyRole('ROLE_ADMIN')"/>  
    ...
     error-page="/my-error-page" />
And the Java configuration:
1
2
3
4
5
6
7
8
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .antMatchers("/admin/*").hasAnyRole("ROLE_ADMIN")
        ...
        .and()
        .exceptionHandling().accessDeniedPage("/my-error-page");
}
When users try to access a resource without having enough authorities, they will be redirected to “/my-error-page“.
5.2. Custom AccessDeniedHandler
Next, let’s see how to write our custom AccessDeniedHandler:
1
2
3
4
5
6
7
8
9
10
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle
      (HttpServletRequest request, HttpServletResponse response, AccessDeniedException ex)
      throws IOException, ServletException {
        response.sendRedirect("/my-error-page");
    }
}
And now let’s configure it using XML Configuration:
1
2
3
4
5
     pattern="/admin/*" access="hasAnyRole('ROLE_ADMIN')"/>
    ...
     ref="customAccessDeniedHandler" />
Or using Java Configuration:
1
2
3
4
5
6
7
8
9
10
11
@Autowired
private CustomAccessDeniedHandler accessDeniedHandler;

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .antMatchers("/admin/*").hasAnyRole("ROLE_ADMIN")
        ...
        .and()
        .exceptionHandling().accessDeniedHandler(accessDeniedHandler)
}
Note how – in our CustomAccessDeniedHandler, we can customize the response as we wish by redirecting or display custom error message.
5.3. REST and Method Level Security
Finally, let’s see how to handle method level security @PreAuthorize@PostAuthorize and@Secure Access Denied.
We’ll of course use the global exception handling mechanism that we discussed earlier to handle the new AccessDeniedException as well:
1
2
3
4
5
6
7
8
9
10
11
@ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler({ AccessDeniedException.class })
    public ResponseEntity handleAccessDeniedException(Exception ex, WebRequest request) {
        return new ResponseEntity(
          "Access denied message here", new HttpHeaders(), HttpStatus.FORBIDDEN);
    }
     
    ...
}
6. Conclusion
This tutorial discussed several ways to implement an exception handling mechanism for a REST API in Spring, starting with the older mechanism and continuing with the Spring 3.2 support and into 4.0 and 4.1.

It’s an Eclipse based project, so it should be easy to import and run as it is.
https://dzone.com/articles/exception-handling-spring-rest

No comments:

Post a Comment

I'm certainly not an expert, but I'll try my hardest to explain what I do know and research what I don't know.

My Favorite Site's List

#update below script more than 500 posts