“We all do”. The well known first reaction of all software developers when they first come to this sentence. But,
of course, not when we’re going to handle errors.
We, as software developers, like to only consider happy paths in our scenarios and consequently, tend to forget the fact
that Error Happens, even more than those ordinary happy cases. Honestly, I can’t even remember when was the last time I got an ok
response from a payment API, those are fail by default.
How did you handle errors while designing and implementing your very last REST API? How did you handle validation errors? How did you deal with uncaught exceptions in your services? If you didn’t answer these questions before, now it’s time to consider answering them before designing a new REST API.
Of course, The most well known and unfortunate approach is let the damn exception propagates until our beloved client sees the
beautiful stacktrace on her client! If she can’t handle our NullPointerException, she shouldn’t call herself a developer.
Forget your platform: It’s an Integration Style
In their amazing book, Enterprise Integration Patterns, Gregor Hohpe and Bobby Woolf described one of the most important aspects of applications as the following:
Interesting applications rarely live in isolation. Whether your sales application must interface
with your inventory application, your procurement application must connect to an auction site,
or your PDA’s PIM must synchronize with the corporate calendar server, it seems like any
application can be made better by integrating it with other applications.
Then they introduced four Integration Styles. Regardless of the fact that the book was mainly about Messaging Patterns, we’re going to focus on Remote Procedure Invocation. Remote Procedure Invocation is an umbrella term for all approaches that expose some of application
functionalities through a Remote Interface, like REST and RPC.
Now that we’ve established that REST is an Integration Style:
Do Not expose your platform specific conventions into the REST API
The whole point of using an Integration Style, like REST, is to let different applications developed in different platforms work together,
hopefully in a more seamless and less painful fashion. The last thing a python client developer wanna see is a bunch of Pascal Case URLs, just because the API was developed with C#. Don’t shove all those Beans into your message bodies just because you’re using Java.
For a moment, forget about the platform and strive to provide an API aligned with mainstream approaches in REST API design, like the
conventions provided by Zalando team.
Use HTTP status codes until you bleed!
Let’s start with a simple rule:
Never ever convey error information in 2xx and 3xx response bodies!
Many so-called RESTful services out there are using HTTP status codes incorrectly. One fundamentally wrong approach is to convey
error information through a 200 OK response. The other common and irresponsible approach is to return the stacktrace as part of 500 Internal Server Error body:
Here is another simple rule to follow unconditionally:
Say goodbye to your stacktrace!
There is a handful of error status codes available on 4xx and 5xx ranges that you can use to signal client or server errors. For example, suppose we have a Geeks API and we can create a new geek using POST on /geeks endpoint. If client didn’t send the required parameters,
we can tell her about the error using a 400 Bad Request response. If authentication is required but client did not send any authentication parameters in her request, we can respond with a 401 Unauthorized response.
Other typical error responses may use 403 Forbidden, 404 Not Found, 429 Too Many Requests, 500 Internal Server Error and 503 Service Unavailable. Of course these are not all available status codes to use, for an exhaustive list of them, consider reading this Wikipedia entry.
Not enough information?
Even when we’re using HTTP status codes to indicate client or server errors, there may be some cases that we need to provide more information about the error. Sometimes we use the same status code to respond for multiple error cases. For example, the POST /geeks endpoint may return 400 Bad Request for both validation errors and when that geek already exists. In such scenarios, How client would be able to tell those errors apart?
One common solution for this problem is to use Application Level Error Codes. These are codes you define to indicate one and only one error case. For example, you may reserve error code 42 to say the geek already exists:
status_code and reason_phrase fields would come handy when someone is testing the API via browser. Anyway, clients can find out what exactly went wrong just by inspecting the error_code field.
Using a numeric error code is the most common approach to implement application level error codes. However, selecting the error code is a one of the main challenges of this approach. If we assign our error codes with no strategy and upfront thinking whatsoever, we may end up with a error code mess. One solution to this challenge is to define Ranges of error codes. For example, if our fictional REST service has two resources, we can define three ranges like the following:
1 to 50 for general error codes, e.g. 1 for invalid JSON payload
51 to 100 for Geeks API (under /geeks), e.g. 51 for geek already exist
101 to 150 for Technologies API (under /technologies)
Since different resources aren’t equally error prone, we may assign different range sizes to different resources. Estimating each range size is the other challenge we should thought through while designing the API.
The other approach which I tend to prefer to the numeric error codes is Resource Based Error Codes. That is, we use a string prefix for each error code and that prefix is determined by the resource itself. For example:
All general errors are prefixed with gen-, e.g. gen-1 for invalid JSON payload
Geeks API errors are prefixed with geeks-, e.g. geeks-1 for geek already exists
Technologies API errors are prefixed with techs-, e.g. techs-1 for whatever
This way our error codes are a little more verbose but wouldn’t have those mentioned challenges:
Be more expressive!
What is your first reaction when you see the following error response?
I bet you would search the API documentation (if any!) like a clueless chicken to find out what the heck this geeks-1 code means? Clients would have a nicer API experience if we provide an error message corresponding to the error code:
If you live in a multilingual context, you could go one step further by changing the error_message language through Content Negotiation process. For example, if the client set the Accept-Language header to fr-FR, the error could be (Google Translate says so):
How about validation errors?
There is this possibility that client has more than one validation error in her request. With our current error response schema, we can only report one error with each http response. That is, in multiple validation errors scenario, first we complain about the first validation error, then the second error and so on. If we could report all validation errors at once, in addition to have a more pleasant API, we could save some extra round trips.
Anyway, we can refactor our error response model to return an array of errors (surprise!) instead of just one error:
“Talk is cheap, show me the code”
Here I’m gonna provide a very minimal implementation of what we’ve talked about so far with Spring Boot. All the following codes are available at github, you can skip rest of the article and check them out now. Anyway, Let’s start with error codes, The ErrorCode interface will act as a super-type for all our error codes:
Each error code has a String based code and a HttpStatus corresponding to that code. There is a default implementation of this interface embedded into the interface itself called UnknownErrorCode. This implementation will be used when we couldn’t figure out what exactly went wrong in the server, which obviously is a server error.
As I said each resource should provide an implementation of this interface to define all its possible error codes. For example, Geeks API did that like the following:
Then we should somehow convert different exceptions to appropriate ErrorCodes. ExceptionToErrorCode strategy interface does that for us:
Again each resource should provide an implementation for this interface per each defined error codes. Geeks API does that like the following:
This basically catch all GeekAlreadyExists exceptions and convert them to GeeksApiErrorCodes.GEEK_ALREADY_EXISTS error code.
Since there would be quite a large number of implementations like this, It’s better to delegate finding the right implementation for each exception to another abstraction. ErrorCodes factory would do that like the following:
Here comes the ErrorResponse class to used to as the JSON representation of each error:
And finally to glue all of these together, a Spring MVC ExceptionHandler would catch all exceptions and render appropriate ErrorResponsees: