Quick Thoughts on Spring Boot Error Handling
[
@ControllerAdvice] can reduce and centralize a great deal of code, allowing for deep introspection surrounding one or two files rather than digging through hundreds of lines of code and error-throwing locales.
Quick Thoughts on Spring Boot Error Handling
The Problem
Spring and Spring Boot are frameworks that allow Java code to very-quickly be assembled into fully-featured back-end services.
Indeed, Spring and Spring Boot have taken over for J2EE and can fulfill the requirements of a J2EE project–at least as far as I’ve seen.
Input and operations can throw errors; all it takes is one errant upstream service to pass back an handled null, or for the request to time out, and you have errors to handle.
A One-Stop Error-Handling Shop
My own approach for error-handling is to handle special events locally, and general events centrally. Viz:
A special event might include a timeout when calling an upstream service–I still back-stop with a general TimeoutException handler and corresponding 503 return, but maybe I want to:
- Re-submit the request before failing,
- Try a fallback URL,
- Execute other fallback logic,
- Eat the error silently.
I would take care of this locally before giving up and letting the error bubble-up to the controller, or another error-handling routing.
A general event might include 400, 401, 403 events–the calling user or system has passed invalid data, failed to provide authentication, or provided incorrect credentials, respectively. I’d want these events handled globally and generically, with context-sensitive text: e.g. you must authenticate to view customers.
This approach can reduce and centralize a great deal of code, allowing for deep introspection surrounding one or two files rather than digging through hundreds of lines of code and error-throwing locales.
Okay, But How?
At my day job, I am surprised when I introduce this notion to many teams and people, they seem unaware that this exists. There are a few Spring annotations that can be used, but I really like–as all my calls come from controllers, the @ControllerAdvice annotation, which lets me write a GlobalErrorHandler.java file that makes use of an ErrorObject model, which typically contains at least these fields:
{
"error": String,
"message": String,
"timestamp": DateTime
}
I backstop all uncaught errors with a 500 status code, and handle the others that I want returned to the client. In the application itself, I get the assurance that all unhandled errors–for calls originating from a @Controller–will eventually bubble back to the @ControllerAdvice and into my GlobalErrorHandler.java file for handling.