Error handling for REST APIs with Spring Boot in servlet and reactive applications
For this week post I initially planned to write about creating Reactive API with Spring Boot Web Flux, but as you know, things don't always go as planned.
One foundamental part of API creation is error handling, and for my projects I found what I think is a great library to standardize this, named error-handling-spring-boot-starter
by Wim Deblauwe.
To me this is a great addition to any application exposing REST services because it intercepts all the unhandled exceptions (but you have tests to prevent this, aren't you? :-) ) and also managed ones, and returns a standard JSON structure to make the errors easier to consume in web applications.
The library manages most of the more common exceptions and it also allows you to add your custom ones in a really easy way, all while preserving the JSON response and without messing up your code.
All you need to do to get all this automagic time saver is to add the library dependency to your project, the Spring Boot AutoConfiguration will handle all the burdens of wiring up the configurations at boot time. You just have to do... nothing else than declaring your own Exceptions and throwing it where needed.
Adding the maven dependency
<dependency>
<groupId>io.github.wimdeblauwe</groupId>
<artifactId>error-handling-spring-boot-starter</artifactId>
<version>LATEST_VERSION_HERE</version>
</dependency>
Adding the gradle dependency
compile 'io.github.wimdeblauwe:error-handling-spring-boot-starter:LATEST_VERSION_HERE'
How the error handling works
As the documentation suggests, by adding the library on the classpath, it will become active. It registers an @ControllerAdvice
bean in the context that will act if an exception is thrown from a @RestController
method.
All you need to do to manage your own custom Exceptions is to declare them like the following:
package com.company.application.user;
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(UserId userId) {
super("Could not find user with id " + userId);
}
}
and you will get a response from the REST service like the following:
{
"code": "USER_NOT_FOUND",
"message": "Could not find user with id 123"
}
Nice, right?
And thsi is not only valid for business errors that lead to exceptions, but also for invalid requests. You can in fact add the @Valid
annotation to your request parameters to require object validation (if a validator is on the classpath), and if something is not right, the library will handle the details for you.
Take the following example from the documentation, where an object is annotated with validation requirements:
public class ExampleRequestBody {
@Size(min = 10)
private String name;
@NotBlank
private String favoriteMovie;
// getters and setters
}
and then used as a parameter for a REST service like the following:
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RestController
@RequestMapping("/example")
public class MyExampleController {
@PostMapping
public MyResponse doSomething(@Valid @RequestBody ExampleRequestBody requestBody ) {
// ...
}
}
if the requestBody param is not valid, for example if the fields are empty, you will get a detailed response explaining what's wrong in your request:
{
"code": "VALIDATION_FAILED",
"message": "Validation failed for object='exampleRequestBody'. Error count: 2",
"fieldErrors": [
{
"code": "INVALID_SIZE",
"property": "name",
"message": "size must be between 10 and 2147483647",
"rejectedValue": ""
},
{
"code": "REQUIRED_NOT_BLANK",
"property": "favoriteMovie",
"message": "must not be blank",
"rejectedValue": null
}
]
}
And you can also customize other details of the response like the status code just by annotating the custom Exception with the @ResponseStatus(<status-code>)
like in the following example:
package com.company.application.user;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.NOT_FOUND) (1)
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException(UserId userId) {
super("Could not find user with id " + userId);
}
}
There are a lot of other features in the library that are explained in the documentation that you can find here:
Using the Error Handling library in Reactive projects
As I said at the beginning of this article, my initial intention was to create a demo project on how to write reactive REST APIs with Spring Boot WebFlux, based on Project Reactor with support for R2DBC (the reactive database access alternative to JDBC) and error handling managed by the aforementioned library, but... I discovered that it was only designed to work with Spring Web, that is the standard Servlet way of creating applications.
I also had a look at the repository to see if there was some Pull Request or Issue related to this requirement, and in fact I found a couple of issues on the topic, but no PRs.
Having access to the source code of a library is an invaluable thing both for work and as a learning tool, because you can understand how things work under the hood and, in case of a problem, what is going wrong. The other great value in Open Source is that you can fix or enhance things when you find something wrong or need something that is missing.
That said, I started looking to the Spring Boot documentation to see how error handling was supposed to be done in a Reactive project and how it was currently handled inside the library, and after a couple of hours of documenting and writing code I completed the addition of the support of the error handler for Reactive projects.
The great part of the addition is that it works seamlessly with the original code and transparently inside the Spring Boot application, so for you the developer, the user experience is exactly the same as before, just add the library dependency to your project and create your custom exception, the error handling is managed the same as in a traditional servlet application.
You can find my pull request here or you can use my fork of the library while waiting for the PR to be accepted.
Update: The PR has been accepted and the reactive support will be included in version 3.0.0 of the error-handling-spring-boot-starter library