Handling exceptions globally

This recipe presents a technique for handling exception globally in a web application.

Getting ready

There are different ways to handle exceptions in Spring MVC. We can choose to define controller-specific @ExceptionHandler or we can choose to register @ExceptionHandler globally in the @ControllerAdvice classes.

We developed the second option in our REST API, even if our CloudstreetApiWCI super-class could have shared @ExceptionHandler among its controllers.

Now we will see how to automatically map custom and generic exception types to HTTP status codes, and how to wrap the right error messages in a generic response object that can be used by any client.

How to do it...

  1. We need a wrapper object to be sent back to the client when an error occurs:
    public class ErrorInfo {
        public final String error;
        public int status;
        public final String date;
    
        private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        public ErrorInfo(Throwable throwable, HttpStatus status){
          this.error = ExceptionUtil.getRootMessage(throwable);
          this.date = dateFormat.format(new Date());
          this.status = status.value();
       }
       public ErrorInfo(String message, HttpStatus status) {
          this.error = message;
          this.date = dateFormat.format(new Date());
          this.status = status.value();
       }
      @Override
      public String toString() {
        return "ErrorInfo [status="+status+", error="+error+ ", date=" + date + "]";
      }
    }
  2. We create a RestExceptionHandler class annotated with @ControllerAdvice. This RestExceptionHandler class also inherits the ResponseEntityExceptionHandler support class, which gives us access to a default mapping exception/response status ready to be overridden:
    @ControllerAdvice
    public class RestExceptionHandler extends ResponseEntityExceptionHandler {
    
       @Override
    protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
    if(body instanceof String){
    return new ResponseEntity<Object>(new ErrorInfo((String) body, status), headers, status);
       }
      return new ResponseEntity<Object>(new ErrorInfo(ex, status), headers, status);
    }
    
        // 400
        @Override
    protected ResponseEntity<Object> handleHttpMessageNotReadable(final HttpMessageNotReadableException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) {
    return handleExceptionInternal(ex, "The provided request body is not readable!", headers, HttpStatus.BAD_REQUEST, request);
    }
    
    @Override
    protected ResponseEntity<Object> handleTypeMismatch(TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
      return handleExceptionInternal(ex, "The request parameters were not valid!", headers, HttpStatus.BAD_REQUEST, request);
      }
    (...)
    
    @ExceptionHandler({ InvalidDataAccessApiUsageException.class, DataAccessException.class , IllegalArgumentException.class })
    protected ResponseEntity<Object> handleConflict(final RuntimeException ex, final WebRequest request) {
        return handleExceptionInternal(ex, "The request parameters were not valid!", new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
    }
    (...)
    
    // 500
    @ExceptionHandler({ NullPointerException.class, IllegalStateException.class })
    public ResponseEntity<Object> handleInternal(final RuntimeException ex, final WebRequest request) {
    return handleExceptionInternal(ex,  "An internal 	error happened during the request! Please try 	again or contact an administrator.", new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR, request);
    }
    }

    Tip

    Both the ErrorInfo wrapper and this RestExceptionHandler will support internationalization. It will be demonstrated in Chapter 7, Developing CRUD Operations and Validations.

  3. We have created the two following property editors for the MarketCode and QuotesInterval Enums:
    public class MarketCodeEditor extends PropertyEditorSupport{
    public void setAsText(String text) {
        try{
          setValue(MarketCode.valueOf(text));
        } catch (IllegalArgumentException e) {
          throw new IllegalArgumentException("The provided value for the market code variable is invalid!");
        }
        }
    }
    public class QuotesIntervalEditor extends PropertyEditorSupport {
        public void setAsText(String text) {
        try{
           setValue(QuotesInterval.valueOf(text));
        } catch (IllegalArgumentException e) {
          throw new IllegalArgumentException("The provided value for the quote-interval variable is invalid!");
        }
      }
    }

    Tip

    These two property editors are automatically registered because they are satisfying a naming and location convention. Since MarketCode and QuotesInterval are enum values, Spring looks for MarketCodeEditor (Editor suffix) and QuotesIntervalEditor in the Enums’ packages.

  4. That's it! You can test it by providing an incorrect market code in the getHistoIndex method of the AngularJS factory (in the home_financial_graph.js file). Change the call from $http.get("/api/indices/"+market+"wrong/"+index+"/histo.json") to $http.get("/api/indices/"+market+"/"+index+"/histo.json").
  5. After restarting the whole application (cloudstreetmarket-webapp and cloudstreetmarket-api), the call to http://localhost:8080/portal/index will induce the Ajax GET request for the index loading to result in a 400 status code:
    How to do it...
  6. More details about this failed request will show up in the json response:
    How to do it...

    The received error message—The provided value for the market variable is invalid! is acceptable for now.

  7. You can reset the home_financial_graph.js file after getting this result.

How it works...

Here, we are focusing on the way we handle exceptions in a REST environment. The expectations are slightly different than in a pure web app because the direct user may not necessarily be a human. For this reason, a REST API has to maintain standard, consistent, and self-explanatory communication even if a process has generated an error or has been unsuccessful.

This consistency is achieved by always returning an appropriate HTTP status code feedback to the client from the server about the request treatment, and by always returning a response body in a format that is expected by the client (a format that matches one of the mime types listed in the Accept header of the HTTP request).

Global exception handling with @ControllerAdvice

Spring 3.2 has brought a solution that is much more suitable to REST environments than the previous exception handling mechanisms. With this solution, classes annotated with @ControllerAdvice can be registered in a different locations of the API. These annotations are looked-up by classpath scanning and are auto-registered in a common repository to support all of the controllers (by default) or subsets of controllers (using the annotation options).

In our case, we defined one single @ControllerAdvice to monitor this entire API. The idea is to define, in the @ControllerAdvice annotated class(es), the relevant methods that can match match specific exception Type(s) to specific ResponseEntity(ies). A ReponseEntity carries a body and a response status code.

These methods to define are annotated with @ExceptionHandler. The options of this annotation allow you to target specific exception Types. A common pattern when defining a @ControllerAdvice is to make it extend the support class ResponseEntityExceptionHandler.

The support ResponseEntityExceptionHandler class

The support ResponseEntityExceptionHandler class provides a predefined mapping between native exceptions (such as NoSuchRequestHandlingMethodException, ConversionNotSupportedException, TypeMismatchException, and so on) and HTTP status codes.

The ResponseEntityExceptionHandler implements a common pattern for response rendering. It invokes case-specific rendering methods declared as protected, such the following handleNoSuchRequestHandlingMethod.

protected ResponseEntity<Object> handleNoSuchRequestHandlingMethod(NoSuchRequestHandlingMethod Exception ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
    pageNotFoundLogger.warn(ex.getMessage());
  return handleExceptionInternal(ex, null, headers, status, request);
}

These methods are obviously fully overridable in the @ControllerAdvice annotated class(es). The important thing is to return the handleExceptionInternal method.

This handleExceptionInternal method is also defined as protected and then overridable. This is what we have done—returned a uniform ErrorInfo instance:

@Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
  return new ResponseEntity<Object>(new ErrorInfo(ex, (body!=null)? body.toString() : null, status), headers, status);
}

A uniform error response object

There are no specific standard practices about the fields that the uniform error response object should expose. We decided to offer the following structure for the ErrorInfo object:

{
  error: "Global categorization error message",
  message: "Specific and explicit error message",
  status: 400,
  date: "yyyy-MM-dd HH:mm:ss.SSS"
}

Using two different levels of messages (the global error message coming from the exception Type and the case-specific message) allows the client side to choose the more appropriate one (or even both!) to be rendered in the application for each situation.

As we already said, this ErrorInfo object doesn't support internationalization yet. We will improve it later in the Chapter 7, Developing CRUD Operations and Validations.

There's more...

We provide here a collection of resources related to exception handling in a web environment:

HTTP Status Codes

The World Wide Web Consortium specifies explicit response status codes for HTTP/1.1. More important than the error messages themselves, it is critical for a REST API to implement them. You can read more about this at:

http://www.w3.org/Protocols/rfc2616/rfc2616-sec010.html

The official article about exception handling in Spring MVC

An article from in the spring.io blog is a very interesting resource. It is not limited to the REST use case. It can be accessed from this address: http://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc.

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset