Error Handling for REST with Spring

In the earlier post of REST with Spring series, we discussed Spring MVC Content Negotiation.In this article, we will discuss how to implement Spring REST Exception Handling.

 

Introduction

Exception handling in a RESTful API with a meaningful error message and the status code is a desired and must have feature.A good error message helps API client to take corrective actions.In this post, we will discuss and implement Exception Handling with Spring for a REST API.

 

1. Restful API Error / Exception Design

While designing exception handling in the RESTful API, it’s a good practice to set HTTP status code in the response to communicating why the request failed.We should send more information along with HTTP status code.It will help the client understand the error and take any corrective action.

HTTP/1.1  404
Content-Type: application/json
{
    "status": 404,
    "error_code": 123,
    "message": "Oops! It looks like that file does not exist.",
    "details": "File resource does not exist.We are working on fixing this issue.",
    "information_link": "https://www.javadevjournal.com/errors/123"
}

Let’s discuss this response to understand important points while designing response for your REST API.

  • The status represents HTTP status code.
  • error_code represents REST API specific error code.This field is helpful to pass on API / domain specific information.
  • The message field represents human-readable error message.
  • The details section represents error information with complete detail.
  • The information_link field specifies a link for detail information about the error or exception.

 

2. Spring  REST Error Handling

Spring and Spring Boot provides a number of options for error/exception handling.

 
2.1 ExceptionHandler Annotation

ExceptionHandler is a Spring annotation handle exceptions thrown by request handling.This annotation works at the @Controller level.

@RestController
public class WelcomeController {

    @GetMapping("/greeting")
    String greeting() throws Exception {
      //
    }

    @ExceptionHandler({Exception.class})
    public  handleException(){
       //
    }
}

There are multiple problems or drawbacks with the approach.

  • This annotation is only active for the given controller.
  • This annotation is not global and we need to add to every controller (not very intuitive).

Most of the enterprise application work by extending a basic controller (having common controller functionalities). We can overcome @ExceptionHandler limitation by adding it to the base controller.This also has multiple limitations

  • The base controller is not suitable for all type of controller. We will end up by duplicating out code.
  •  Our Controllers may have to extend a third part class which is not under our control.

With all these limitations, it is not recommended to use this approach while building your RESTful API

 

3. The @ControllerAdvice Annotation

Spring 3.2 introduced @ControllerAdvice annotation which supports global Exception handler mechanism.A controller advice allows you to use exactly the same exception handling techniques but applies them across the application, not just to an individual controller.Before moving ahead with our discussion, let’s create a class to hold information about errors or exceptions. 

public class ApiErrorResponse {

    private HttpStatus status;
    private String error_code;
    private String message;
    private String detail;
    
    // getter and setters
   //Builder 
    public static final class ApiErrorResponseBuilder {
        private HttpStatus status;
        private String error_code;
        private String message;
        private String detail;

        private ApiErrorResponseBuilder() {
        }

        public static ApiErrorResponseBuilder anApiErrorResponse() {
            return new ApiErrorResponseBuilder();
        }

        public ApiErrorResponseBuilder withStatus(HttpStatus status) {
            this.status = status;
            return this;
        }

        public ApiErrorResponseBuilder withError_code(String error_code) {
            this.error_code = error_code;
            return this;
        }

        public ApiErrorResponseBuilder withMessage(String message) {
            this.message = message;
            return this;
        }

        public ApiErrorResponseBuilder withDetail(String detail) {
            this.detail = detail;
            return this;
        }

        public ApiErrorResponse build() {
            ApiErrorResponse apiErrorResponse = new ApiErrorResponse();
            apiErrorResponse.status = this.status;
            apiErrorResponse.error_code = this.error_code;
            apiErrorResponse.detail = this.detail;
            apiErrorResponse.message = this.message;
            return apiErrorResponse;
        }
    }
}

We have already explained meaning or significance of each field defined in the  ApiErrorResponse class.Here is a simple example

@ControllerAdvice
public class GlobalRestExceptionHandler extends ResponseEntityExceptionHandler {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(Exception.class)
    public void defaultExceptionHandler() {
        // Nothing to do
    }
}

 

3.1 ResponseEntityExceptionHandler

A convenient base class for @ControllerAdvice classes that wish to provide centralized exception handling across all @RequestMapping methods through @ExceptionHandler methods.This is a convenient way while working on Spring based REST API since it allows the developer to specify ResponseEntity as return values.

Let’s work on some most common client errors. We will look into few scenarios of a client sending an invalid request.

3.2  MethodArgumentTypeMismatchException

This exception is thrown when method arguments are not the expected type

@ExceptionHandler({MethodArgumentTypeMismatchException.class})
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request){

     ApiErrorResponse response =new ApiErrorResponse.ApiErrorResponseBuilder()
        .withStatus(status)
        .withError_code(status.BAD_REQUEST.name())
        .withMessage(ex.getLocalizedMessage()).build();

        return new ResponseEntity<>(response, response.getStatus());
    }
3.3  HttpMessageNotReadable

This exception is thrown when API is not able to read the HTTP message

@Override
@ExceptionHandler({HttpMessageNotReadableException.class})
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { String error = "Malformed JSON request "; ApiErrorResponse response =new ApiErrorResponse.ApiErrorResponseBuilder() .withStatus(status) .withError_code("BAD_DATA") .withMessage(ex.getLocalizedMessage()) .withDetail(error+ex.getMessage()) .build(); return new ResponseEntity<>(response, response.getStatus()); }

Below we can see the answer to a REST call

{
"status": "BAD_REQUEST",
"error_code": "BAD_DATA",
"message": "JSON parse error: Unexpected character 
"detail": "Malformed JSON request JSON parse error: Unexpected character ('<' (code 60)): expected a valid value (number, String, array, object, 'true', 'false' or 'null');
}
 
3.4  Handling Custom Exceptions

While working on REST API, we may come across multiple use cases when a request is not fulfilled completely and we want to return a custom exception back to the client API.

Let’s take a simple use case when client API call to find a record by its unique id using a repository class.Our repository class can return a null or empty object if the object is not found.In this case, if not handled correctly, our API will return 200 (OK) response to the client even if the resource is not found.To handle all similar use cases, we create a custom exception and handle this exception in our GlobalRestExceptionHandler

@ExceptionHandler(CustomServiceException.class)
protected ResponseEntity<Object> handleCustomAPIException(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
  
   ApiErrorResponse response =new ApiErrorResponse.ApiErrorResponseBuilder()
         .withStatus(status)
         .withError_code(HttpStatus.NOT_FOUND.name())
         .withMessage(ex.getLocalizedMessage())
         .withDetail(ex.getMessage())
         .build();
        return new ResponseEntity<>(response, response.getStatus());
 }

I will not go into details about handling different Exceptions in the REST API since all Exceptions can be handled in a similar way as explained above.Here is the list of some of the common exceptions in a REST API.

  • HttpMediaTypeNotSupportedException
  • HttpRequestMethodNotSupportedException
  • TypeMismatchException

 

3.5  Default Exception Handler

We can not handle each exception within the system.Let’s create a fallback exception handler which will handle all exceptions that don’t have specific exception handler.

@ControllerAdvice
public class GlobalRestExceptionHandler extends ResponseEntityExceptionHandler {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(Exception.class)
    public void defaultExceptionHandler() {
        // Nothing to do
    }
}

 

4. Spring Boot REST Exception Handling

Spring Boot provides a number of features to build RESTful API’s. Spring Boot 1.4 introduced the @RestControllerAdvice annotation for easier exception handling.It is a convenience annotation that is itself annotated with @ControllerAdvice and @ResponseBody.Here is an example

@RestControllerAdvice
public class RestExceptionHandler {

@ExceptionHandler(CustomNotFoundException.class)
public ApiErrorResponse handleNotFoundException(CustomNotFoundException ex) {

ApiErrorResponse response =new ApiErrorResponse.ApiErrorResponseBuilder()
      .withStatus(HttpStatus.NOT_FOUND)
      .withError_code("NOT_FOUND")
      .withMessage(ex.getLocalizedMessage()).build();
      
    return responseMsg;
    }
}

While using above approach, you should set following property to true in Spring Boot application.properties file

spring.mvc.throw-exception-if-no-handler-found=true # Whether a "NoHandlerFoundException" thrown if no Handler was found to process a request.

 

Summary

It is important to handle and process exceptions properly in the Spring bases REST API.In this post, we covered different options to implement Spring REST Exception Handling.

Building a good exception handling workflow for REST API is an iterative and complex process.A good exception handling mechanism allows API client to know what went wrong with the request.

Umesh

Hello!! I am Umesh- an engineer by profession and a photographer by passion.I like to build stuff on the web using OSS and love to capture the world through my lens.

follow me on:

9
Leave a Reply

avatar
3 Comment threads
6 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
4 Comment authors
Umesh AwasthiAndresEnriquerichwxd Recent comment authors

This site uses Akismet to reduce spam. Learn how your comment data is processed.

  Subscribe  
newest oldest most voted
Notify of
richwxd
Guest
richwxd

This error handling does not support cross domain. So I have to convert this to forward a general successful rest controller, then transfer the error message to that.

Umesh Awasthi
Admin
Umesh Awasthi

Not sure if I understood your question completely, but for with Spring Boot this can be easily taken care by using either @CrossOrigin annotation or per case basis, alternatively you can opt for more global solution like public void addCorsMappings(CorsRegistry registry). Hope this will answer your question.

Enrique
Guest
Enrique

Hello Admin share the above code on github. Otherwise the code is very helpful.

Umesh Awasthi
Admin
Umesh Awasthi

Hello Enrique,

Thanks for your feedback and suggestion. I will be sharing this code shortly on the GitHub

Enrique
Guest
Enrique

Thank you

Andres
Guest
Andres

Hello Umesh, where is the gitHub code?

Umesh Awasthi
Admin
Umesh Awasthi

Hey Andres,

I missed to update it. Will be doing it over the weekend.There are few things which needs to be updated before I can push the code on Github

trackback
Data Conversion for Spring REST API | Java Development Journal

[…] are not handling exception cases. Read our article on Error Handling for REST with Spring for […]

trackback
Spring Interview Questions | Java Development Journal

[…] For more information please read […]