Cette page n'est pas disponible en français. Veuillez-nous en excuser.

ABIS Infor - 2022-01

Clean Exception Handling in Spring Boot APIs

Sandy Schillebeeckx (ABIS) - 11 January 2022

Summary

Like in any other program, good exception handling is key when writing APIs. In this case, you don't only want to create readable code for the developers. It should also be clear for the users of the API what to expect when something is going wrong. In this article, we will focus on both aspects, using Spring Boot as the Java API framework.

API Exception Handling good practices

When writing APIs, it is essential that your exception handling is very clear for the users to understand, such that they can handle them correctly later on. Each exception should have an easily readable response body (in JSON presumably), but also the returned HTTP status code should clearly represent the error passed to the client.

As the HTTP status code you could, of course, always pass a "400: bad request", but try to keep that one for obvious client errors, like a malformed request syntax. There are plenty more choices when it comes to 4xx codes which you can choose from, like the following:

  • 404: NOT FOUND – the obvious one to use when a requested resource is not found
  • 409: CONFLICT – I tend to use this when a POST/PUT/DELETE fails
  • 401/403: UNAUTHORIZED/FORBIDDEN – for unauthenticated/unauthorized requests

Next to these "user errors", also codes like 405, 406, 408, 416 (and the general 500), will arise, but these will be more "technical" issues thrown by the API itself, and are up to the developers to handle. These will be out of scope in this article.

In case an exception is returned, it should also always contain a response body with a JSON message mimicking the error which is returned. Officially, there is still no standardized way to write those. As you look at errors returned from Google, they will be different than those returned by AWS. The IETF however, is making a good effort in developing a standard, called the RFC7807 specification.

Each exception message should contain the following 5 parts:

  • title (String): short, human-readable title for the general error
  • statuscode (int): HTTP status code of the error
  • detail (String): human-readable description of the specific error
  • type (String, optional): URL to a document describing the error
  • instance(String, optional): URI pointing to error log for that specific response

Let me give you a JSON example to make this clear:

	{
"title": "person not found",
"status": 404,
"description": "person with id 100 not found",
"type": "/errors/incorrect-userid",
"instance": "persons/100"
}

In Java, this could then be represented by the following class:

	public class ApiError {
private String title;
private int status;
private String description;
private String type = "about:blank";
private String instance = "";
public ApiError() {}
public ApiError(String title, int status, String description) {
this.title = title;
this.status = status;
this.description = description;
}
// the getters and setters (or equivalent using Lombok)
}

Even when following all these rules, don't forget to document everything of course. This can be easily done using Swagger or an alternative documentation framework.

Keeping your code clean using centralized Exception Handlers

Now that the exceptions are clear to your users, also don't forget about your fellow developers. Don't they also deserve some nice, readable code?

Exception handling is a cross-cutting concern, which is prone to pollute your code. Spring Boot offers you a technique that helps you keeping your code clean. Based on AOP (Aspect Oriented Programming), you can create a class annotated with @RestControllerAdvice, which will keep your Controller class free of error handling code. Let's look at that in practice.

We want to translate the "Person not found" case, which we presented above, into code.
The controller class will be the following:

@RestController
@RequestMapping("personapi/persons")
public class PersonController {
@Autowired
PersonService ps;
@GetMapping("{id}")
public Person findPerson(@PathVariable("id") int id)
throws PersonNotFoundException {
return ps.findPerson(id);
} }

So, except for the "throws PersonNotFoundException", there is nothing present there that mentions the ApiError which it should translate to in case something goes wrong. This we will do in the following class:

@RestControllerAdvice
public class MyRestExceptionHandler extends ResponseEntityExceptionHandler {

@ExceptionHandler(value = PersonNotFoundException.class)
protected ResponseEntity<? extends Object>
handlePersonNotFound(PersonNotFoundException pnfe, WebRequest request) {
HttpStatus status = HttpStatus.NOT_FOUND;
ApiError err = new ApiError("guest not found", status.value(),
pnfe.getMessage());
return new ResponseEntity<ApiError>(err,new HttpHeaders(),status);
}
}

This code will then be automatically woven into the controller code, at the moment our exception is thrown. Looks nice and simple, doesn't it?

Conclusion

Spring Boot provides us with a clean way of performing exception handling in APIs. This helps us to apply all the good practices mentioned in the first paragraph. You will make both your users and your developers very happy!

In case you want to learn more about programming APIs with Spring Boot, you are always welcome at our training Building REST APIs with Spring Boot.