Error handling is a very important part of a reliable and user-friend API. If you think of your API as a product, when something goes wrong with the product, it should clearly indicate the error to users and the reason why. Also, it should be able to guide users to avoid that error from happening again.
For example, if you have an endpoint to create a user: [POST] /v1/users, which expects a valid email and a min-6 letters password and when user sends a too short password, server should returns a 400 Bad request with the body including a translated error message like this: "Password must be at least 6 letters".
This is nice, because then your mobile or webapp can check for the status code and if it's not 2xx then it knows right away that something wrong has happen and instead of proceeding to the next screen on mobile app, it displays the error message to user.
This post will present the strategy that I often use for API error handling and how to implement it with Spring boot app.
- Status code should be used extensively to indicate the error category. For example, 400 Bad request should indicate an error from client side such as invalid input, failed validation… while 401 Unauthorized, as the name suggests is for error with user's authorization (invalid email or password when logging in or access token is expired). On the other hand, 5xx error should be used when something wrong happens from server's side.
- Error message should be localized to user's language so client can use it to display to users.
- For form field validation, error message should be linked with the field name, so client can parse and display the error message right under the form's field. This is very intuitive and easy for users.
- If internal server error happens, the backend should catch it and log the error so we can debug later. More importantly, it should not return internal error to client but instead, just return a generic message like: "Something wrong happens. Please try again later.". Of course in this case, the status code should be 500 Internal Server Error.
Implementation with Spring boot:
The above strategy is very easy to be implemented with Spring Boot's Controller Advice. Specifically, you can have a centralized place to handle exception thrown from inside controller's classes (i.e endpoints). This class will be in charged of translating the error message to user's language as well.
In addition, we do need a generic exception handler for all uncaught exception, which should be internal server error. This handler will log exception's stack trace (and additionally, can send alarm to tech team) and just return a generic error message.
An example of API error can be as followed:
Then we have the controller advice to handle all kinds of exception:
And we can have a base class for exception handler with all common tools for translation and error response builder:
Here we use Spring's message source to do the translation and as we do not need translation for all messages or not all messages are available in all languages, we handle the NoSuchMessageException manually and just return the message itself. Alternatively, you can fallback to the default language of your app.
Also, it's worth noting that locale here is retrieved from LocaleContextHolder, which is constructed from the header Accept-Language of the request.
The last piece of the implementation is to handle uncaught exception (internal server error, for example) so we can be sure no weird internal error is returned to clients.
It's very important to annotate this class with lowest precedence, otherwise all exception will be handled here first. We actually only want to use this class, if the specific exception is not handled elsewhere in other controller advice.
To conclude, in this article, I have shown how we can properly implement API error handling for Spring boot app.
How is your experience with error handling for Spring Boot's backend app? Feel free to leave your comments here.