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.
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:
||Gets information about a bird and throws an exception if not found.|
||This call also gets information about a bird, except it doesn’t throw an exception in case that the bird is not found.|
||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
/birdsendpoint with the following JSON object, that has the string “aaa” on the field “mass,” which should be expecting an integer:
The Spring Boot default answer, without proper error handling:
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
DefaultErrorAttributesfrom Spring Boot. The
timestampfield is an integer number that doesn’t even carry information of what measurement unit the timestamp is in. The
exceptionfield 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
Ok, so let’s define a class for representing API errors. We’ll be creating a class called
ApiErrorthat has enough fields to hold relevant information about errors that happen during REST calls.
statusproperty 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.
timestampproperty holds the date-time instance of when the error happened.
messageproperty holds a user-friendly message about the error.
debugMessageproperty holds a system message describing the error in more detail.
subErrorsproperty 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
ApiSubErrorclass is used to encapsulate those.
So then the
ApiValidationErroris a class that extends
ApiSubErrorand 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
Here is another example of JSON returned when issuing a
POST /birdscall with an invalid value for the bird’s mass:
Let’s explore some of the Spring annotations that will be used to handle exceptions.
RestControlleris the base annotation for classes that handle REST operations.
ExceptionHandleris 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
@ExceptionHandleron methods of
@ControllerAdviceclasses so that the exception handling will be applied globally or to a subset of controllers.
ControllerAdviceis an annotation introduced in Spring 3.2, and as the name suggests, is “Advice” for multiple controllers. It is used to enable a single
ExceptionHandlerto 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
basePackages(). If no selectors are provided, then the
ControllerAdviceis applied globally to all controllers.
So by using
@ControllerAdvice, we’ll be able to define a central point for treating exceptions and wrapping them up in an
ApiErrorobject with better organization than the default Spring Boot error handling mechanism.
Handling ExceptionsThe next step is to create the class that will handle the exceptions. For simplicity, we are calling it
RestExceptionHandlerand it must extend from Spring Boot’s
ResponseEntityExceptionHandler. We’ll be extending
ResponseEntityExceptionHandleras 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
handleHttpMessageNotWritable(). Let’s first see how can we extend
HttpMessageNotReadableExceptionexceptions. We just have to override the method
We have declared that in case of a
HttpMessageNotReadableExceptionbeing thrown, the error message will be “Malformed JSON request” and the error will be encapsulated inside the
ApiErrorobject. Below we can see the answer of a REST call with this new method overridden:
Now we’ll see how to create a method that handles an exception that is not yet declared inside Spring Boot’s
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
nullif 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
That said, let’s create an
ExceptionHandlerfor this newly created
RestExceptionHandlerclass. To do that, create a method called
handleEntityNotFound()and annotate it with
@ExceptionHandler, passing the class object
EntityNotFoundException.classto it. This signalizes Spring that every time
EntityNotFoundExceptionis 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
Localeand others as described here. We’ll just provide the exception
EntityNotFoundExceptionitself as a parameter for this
Great! In the
handleEntityNotFound()method, we are setting the HTTP status code to
NOT_FOUNDand using the new exception message. Here is what the response for the
GET /birds/2endpoint looks like now:
It is important to get in control of the exception handling so we can properly map those exceptions to the
ApiErrorobject 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
ConstraintViolationExceptionand others in the GitHub code.
Here are some additional resources that helped in the composition of this article: