Handling errors correctly in APIs while providing meaningful error messages is a very desirable feature, as it can help the API client properly respond to issues. The default behavior tends to be returning stack traces that are hard to understand and ultimately useless for the API client. Partitioning the error information into fields also enables the API client to parse it and provide better error messages to the user. In this article, we will cover how to do proper error handling when building a REST API with Spring Boot.

Person confused about a cryptic and long error message

Building REST APIs with Spring became the standard approach for Java developers during the last couple of years. Using Spring Boot helps substantially, as it removes a lot of boilerplate code and enables auto-configuration of various components. We will assume that you’re familiar with the basics of API development with those technologies before applying the knowledge described here. If you are still unsure about how to develop a basic REST API, then you should start with this article about Spring MVC or another one about building a Spring REST Service.

Making Error Responses Clearer

Throughout this article, we’ll be using the source code hosted on GitHub of an application that implements a REST API for retrieving objects that represent birds. It has the features described in this article and a few more examples of error handling scenarios. Here’s a summary of endpoints implemented in that application:

GET /birds/{birdId} Gets information about a bird and throws an exception if not found.
GET /birds/noexception/{birdId} This call also gets information about a bird, except it doesn’t throw an exception in case that the bird is not found.
POST /birds Creates a bird.

 

The Spring framework MVC module comes with some great features to help with error handling. But it is left to the developer to use those features to treat the exceptions and return meaningful responses to the API client.

Let’s look at an example of the default Spring Boot answer when we issue an HTTP POST to the /birds endpoint with the following JSON object, that has the string “aaa” on the field “mass,” which should be expecting an integer:

{
 "scientificName": "Common blackbird",
 "specie": "Turdus merula",
 "mass": "aaa",
 "length": 4
}

The Spring Boot default answer, without proper error handling:

{
 "timestamp": 1500597044204,
 "status": 400,
 "error": "Bad Request",
 "exception": "org.springframework.http.converter.HttpMessageNotReadableException",
 "message": "JSON parse error: Unrecognized token 'three': was expecting ('true', 'false' or 'null'); nested exception is com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'aaa': was expecting ('true', 'false' or 'null')\n at [Source: java.io.PushbackInputStream@cba7ebc; line: 4, column: 17]",
 "path": "/birds"
}

Well… the response message has some good fields, but it is focused too much on what the exception was. By the way, this is the class DefaultErrorAttributes from Spring Boot. The timestamp field is an integer number that doesn’t even carry information of what measurement unit the timestamp is in. The exception field is only interesting to Java developers and the message leaves the API consumer lost in all the implementation details that are irrelevant to them. And what if there were more details that we could extract from the exception that the error originated from? So let’s learn how to treat those exceptions properly and wrap them into a nicer JSON representation to make life easier for our API clients.

As we’ll be using Java 8 date and time classes, we first need to add a Maven dependency for the Jackson JSR310 converters. They take care of converting Java 8 date and time classes to JSON representation using the @JsonFormat annotation:

<dependency>
   <groupId>com.fasterxml.jackson.datatype</groupId>
   <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

Ok, so let’s define a class for representing API errors. We’ll be creating a class called ApiError that has enough fields to hold relevant information about errors that happen during REST calls.

class ApiError {

   private HttpStatus status;
   @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
   private LocalDateTime timestamp;
   private String message;
   private String debugMessage;
   private List<ApiSubError> subErrors;

   private ApiError() {
       timestamp = LocalDateTime.now();
   }

   ApiError(HttpStatus status) {
       this();
       this.status = status;
   }

   ApiError(HttpStatus status, Throwable ex) {
       this();
       this.status = status;
       this.message = "Unexpected error";
       this.debugMessage = ex.getLocalizedMessage();
   }

   ApiError(HttpStatus status, String message, Throwable ex) {
       this();
       this.status = status;
       this.message = message;
       this.debugMessage = ex.getLocalizedMessage();
   }
}
  • The status property holds the operation call status. It will be anything from 4xx to signalize client errors or 5xx to mean server errors. A common scenario is a http code 400 that means a BAD_REQUEST, when the client, for example, sends an improperly formatted field, like an invalid email address.

  • The timestamp property holds the date-time instance of when the error happened.

  • The message property holds a user-friendly message about the error.

  • The debugMessage property holds a system message describing the error in more detail.

  • The subErrors property holds an array of sub-errors that happened. This is used for representing multiple errors in a single call. An example would be validation errors in which multiple fields have failed the validation. The ApiSubError class is used to encapsulate those.

abstract class ApiSubError {

}

@Data
@EqualsAndHashCode(callSuper = false)
@AllArgsConstructor
class ApiValidationError extends ApiSubError {
   private String object;
   private String field;
   private Object rejectedValue;
   private String message;

   ApiValidationError(String object, String message) {
       this.object = object;
       this.message = message;
   }
}

So then the ApiValidationError is a class that extends ApiSubError and expresses validation problems encountered during the REST call.

Below, you’ll see some examples of JSON responses that are being generated after we have implemented the improvements described here, just to get an idea of what we’ll have by the end of this article.

Here is an example of JSON returned when an entity is not found while calling endpoint GET /birds/2:

{
 "apierror": {
   "status": "NOT_FOUND",
   "timestamp": "18-07-2017 06:20:19",
   "message": "Bird was not found for parameters {id=2}"
 }
}

Here is another example of JSON returned when issuing a POST /birds call with an invalid value for the bird’s mass:

{
 "apierror": {
   "status": "BAD_REQUEST",
   "timestamp": "18-07-2017 06:49:25",
   "message": "Validation errors",
   "subErrors": [
     {
       "object": "bird",
       "field": "mass",
       "rejectedValue": 999999,
       "message": "must be less or equal to 104000"
     }
   ]
 }
}

Spring Boot Error Handling

Let’s explore some of the Spring annotations that will be used to handle exceptions.

RestController is the base annotation for classes that handle REST operations.

ExceptionHandler is a Spring annotation that provides a mechanism to treat exceptions that are thrown during execution of handlers (Controller operations). This annotation, if used on methods of controller classes, will serve as the entry point for handling exceptions thrown within this controller only. Altogether, the most common way is to use @ExceptionHandler on methods of @ControllerAdvice classes so that the exception handling will be applied globally or to a subset of controllers.

ControllerAdvice is an annotation introduced in Spring 3.2, and as the name suggests, is “Advice” for multiple controllers. It is used to enable a single ExceptionHandler to be applied to multiple controllers. This way we can in just one place define how to treat such an exception and this handler will be called when the exception is thrown from classes that are covered by this ControllerAdvice. The subset of controllers affected can defined by using the following selectors on @ControllerAdvice: annotations(), basePackageClasses(), and basePackages(). If no selectors are provided, then the ControllerAdvice is applied globally to all controllers.

So by using @ExceptionHandler and @ControllerAdvice, we’ll be able to define a central point for treating exceptions and wrapping them up in an ApiError object with better organization than the default Spring Boot error handling mechanism.

Handling Exceptions

Representation of what happens with a successful and failed REST client call

The next step is to create the class that will handle the exceptions. For simplicity, we are calling it RestExceptionHandler and it must extend from Spring Boot’s ResponseEntityExceptionHandler. We’ll be extending ResponseEntityExceptionHandler as it already provides some basic handling of Spring MVC exceptions, so we’ll be adding handlers for new exceptions while improving the existing ones.

Overriding Exceptions Handled In ResponseEntityExceptionHandler

If you take a look into the source code of ResponseEntityExceptionHandler, you’ll see a lot of methods called handle******() like handleHttpMessageNotReadable() or handleHttpMessageNotWritable(). Let’s first see how can we extend handleHttpMessageNotReadable() to handle HttpMessageNotReadableException exceptions. We just have to override the method handleHttpMessageNotReadable() in our RestExceptionHandler class:

@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {

   @Override
   protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
       String error = "Malformed JSON request";
       return buildResponseEntity(new ApiError(HttpStatus.BAD_REQUEST, error, ex));
   }

   private ResponseEntity<Object> buildResponseEntity(ApiError apiError) {
       return new ResponseEntity<>(apiError, apiError.getStatus());
   }

   //other exception handlers below

}

We have declared that in case of a HttpMessageNotReadableException being thrown, the error message will be “Malformed JSON request” and the error will be encapsulated inside the ApiError object. Below we can see the answer of a REST call with this new method overridden:

{
 "apierror": {
   "status": "BAD_REQUEST",
   "timestamp": "21-07-2017 03:53:39",
   "message": "Malformed JSON request",
   "debugMessage": "JSON parse error: Unrecognized token 'aaa': was expecting ('true', 'false' or 'null'); nested exception is com.fasterxml.jackson.core.JsonParseException: Unrecognized token 'aaa': was expecting ('true', 'false' or 'null')\n at [Source: java.io.PushbackInputStream@7b5e8d8a; line: 4, column: 17]"
 }
}

Handling Custom Exceptions

Now we’ll see how to create a method that handles an exception that is not yet declared inside Spring Boot’s ResponseEntityExceptionHandler.

A common scenario for a Spring application that handles database calls is to have a call to find a record by its ID using a repository class. But if we look into the CrudRepository.findOne() method, we’ll see that it returns null if an object is not found. That means that if our service just calls this method and returns directly to the controller, we’ll get an HTTP code 200 (OK) even if the resource isn’t found. In fact, the proper approach is to return a HTTP code 404 (NOT FOUND) as specified in the HTTP/1.1 spec.

To handle this case, we’ll be creating a custom exception called EntityNotFoundException. This one is a custom created exception and different from javax.persistence.EntityNotFoundException, as it provides some constructors that ease the object creation, and one may choose to handle the javax.persistence exception differently.

Example of a failed REST call

That said, let’s create an ExceptionHandler for this newly created EntityNotFoundException in our RestExceptionHandler class. To do that, create a method called handleEntityNotFound() and annotate it with @ExceptionHandler, passing the class object EntityNotFoundException.class to it. This signalizes Spring that every time EntityNotFoundException is thrown, Spring should call this method to handle it. When annotating a method with @ExceptionHandler, it will accept a wide range of auto-injected parameters like WebRequest, Locale and others as described here. We’ll just provide the exception EntityNotFoundException itself as a parameter for this handleEntityNotFound method.

@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
  
   //other exception handlers
  
   @ExceptionHandler(EntityNotFoundException.class)
   protected ResponseEntity<Object> handleEntityNotFound(
           EntityNotFoundException ex) {
       ApiError apiError = new ApiError(NOT_FOUND);
       apiError.setMessage(ex.getMessage());
       return buildResponseEntity(apiError);
   }
}

Great! In the handleEntityNotFound() method, we are setting the HTTP status code to NOT_FOUND and using the new exception message. Here is what the response for the GET /birds/2 endpoint looks like now:

{
 "apierror": {
   "status": "NOT_FOUND",
   "timestamp": "21-07-2017 04:02:22",
   "message": "Bird was not found for parameters {id=2}"
 }
}

Conclusion

It is important to get in control of the exception handling so we can properly map those exceptions to the ApiError object and provide important information that allows API clients to know what happened. The next step from here would be to create more handler methods (the ones with @ExceptionHandler) for exceptions that are thrown within the application code. There are more examples for some other common exceptions like MethodArgumentTypeMismatchException, ConstraintViolationException and others in the GitHub code.

Here are some additional resources that helped in the composition of this article:

Understanding the Basics

Why should the API have a uniform error format?

So that the API client can properly parse the error object. A more complex error could make an implementation of the ApiSubError class and provide more details about the problem so that the client can know which actions to take.

About the author

Bruno Leite, Brazil
member since March 10, 2014
Bruno is a full-stack applications architect and senior developer with more than ten years of experience working with startups and agile teams. He works as an architect or leader developer with proven ability to solve challenging problems and is always looking to contribute to team synergy and growth. [click to continue...]
Hiring? Meet the Top 10 Freelance Java Developers for Hire in September 2017

Comments

Daniel Echevarria Iparraguirre
This article is awesome, great job bro!
Bruno
Thanks Daniel! Glad you liked it.
Jordan Demeulenaere
Instead of using the @JsonFormat annotation, you could simply set the following property in your application.yml : spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS: false This will serialize dates in the ISO 8601 format, which IMO is better than what you have because : 1) You don't pollute your data objects with annotations 2) You also have the time zone information (which is needed when the server and client are in different zones) 3) If needed, your javascript frontend could directly create a Date object instead of parsing the date yourself : let date = new Date(response.timestamp);
Bruno
Great! Good tip Jordan. You are right, better to use the ISO format.
Sujit Tripathy
Hi Bruno Thanks for the nice article. I have a question on how to log the error in RestExceptionHandler to identify which API call cause the error. I think the logger.exception can be added to print the ApiError details however I am not sure how to identify the API request url to map which exception caused by which API call for production debugging. Do you have any thought?
Bruno
Hey Sujit! Yes, I've tested here and was able to get the path and HTTP method by casting the WebRequest to ServletWebRequest <pre><code> ServletWebRequest servletWebRequest = (ServletWebRequest) request; log.error("{} to {}", servletWebRequest.getHttpMethod(), servletWebRequest.getRequest().getServletPath()); </code></pre> I've updated the Github code. Please check the method 'handleHttpMessageNotReadable' from RestExceptionHandler.
Sujit Tripathy
Great! Thanks Bruno.
Luiz Roberto Lethang Rodolpho
Great article Bruno!
sushil gc
Great Article. Love it. Will be implementing it in upcoming projects
Artur Skrzydło
Great article with great examples
Bruno
Thanks bro!
Naveen Kumar
one of the best article to understand Exception Handling with simple example
ottoman91
This is an amazing article. The level of detail in the explanation is just, brilliant. I am currently building API services for an SMS gateway product, and have to create a number of custom error messages for the clients. Your post shall hopefully make it very enjoyable and fun for me to implement this!
ottoman91
hi Bruno I have a query. I am trying to override the handleMissingServletRequestParameter to return the error in the custom JSON format that you have stated in the tutorial. However, the ApiError object is not being returned. Instead, on Postman, I can see the 500 Internal Service Error being displayed at the tab, but nothing is being displayed in the body. Rather, the ApiError object is only being displayed for custom exceptions that I have handled. Any guidance as to what I might be doing wrong in this case? Thanks.
Bruno
Hello! So you were able to return the ApiError for custom exceptions but somehow for the handleMissingServletRequestParameter overriden method it is not working? Can you please show me the code for the overriden method so I can take a look? Thanks for liking the article. Cheers.
ottoman91
HI Bruno. Sure. @Override protected ResponseEntity<Object> handleMissingServletRequestParameter( MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST); String error = ex.getParameterName() + " parameter is missing"; apiError.setMessage(error); apiError.setDebugMessage(ex.getLocalizedMessage()); return buildResponseEntity(apiError); }
ottoman91
Also, Bruno, I am creating my project on top of this SMS Gateway application. https://github.com/openMF/message-gateway/tree/master/src/main/java/org/fineract/messagegateway/exception. I am thinking that whether the default error handlers are not being overriden because of some exceptions that have previously been declared? If you could assist me in how to diagnose this, that would be great. Thanks!
ottoman91
i also tried another iteration of the handleMissingServletRequestParamater, as highlighted below, but even that is not working. @Override protected ResponseEntity<Object> handleMissingServletRequestParameter( MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { String error = ex.getParameterName() + " parameter is missing"; return buildResponseEntity(new ApiError(BAD_REQUEST, error, ex)); } Also, I am attaching the screenshot of when I submit the incomplete query via postman. As you can see, the Status is displayed in the upper right corner of the lower box, but the body display is blank. https://uploads.disquscdn.com/images/a1b8ce0c5853fe82c933e6f82a4f791bd429bcceb6438296aca4228af54c4186.png
monsterTUT
this interview guide has a good breakdown of topics too about restapi https://monstertut.com/spring-boot-interview/
Bruno
Hello @ottoman91:disqus . I've updated the sample code (https://github.com/brunocleite/spring-boot-exception-handling) with an example. Just have to call `http://localhost:8080/birds/params` without params. The `handleMissingServletRequestParameter` method is called when a RequestParam is missing. You can try putting a breakpoint on `handleException(Exception ex, WebRequest request)` method of `ResponseEntityExceptionHandler` class to see if is it catching the exception and make sure that there isn't another exception being called before that one.
comments powered by Disqus
Subscribe
The #1 Blog for Engineers
Get the latest content first.
No spam. Just great engineering posts.
The #1 Blog for Engineers
Get the latest content first.
Thank you for subscribing!
Check your inbox to confirm subscription. You'll start receiving posts after you confirm.
Trending articles
Relevant Technologies
About the author
Bruno Leite
Java Developer
Bruno is a full-stack applications architect and senior developer with more than ten years of experience working with startups and agile teams. He works as an architect or leader developer with proven ability to solve challenging problems and is always looking to contribute to team synergy and growth.