June 22, 2020

Guide to Spring Boot REST API Error Handling

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.

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. To get indept knowledge Spring boot online training .Here’s a summary of endpoints implemented in that application:

</tr>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 /birdsCreates 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.If you want learn anything more Spring boot online training in hyderabad.

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. For more information visit Spring boot course.