REST Error Handling
What is the best way to do REST Error Handling? I have struggled with this over the years. Let me present the typical scenario that I have seen. Your team starts working on some shiny new web service, and of course it is going to be REST. In the initial development, there are lots of errors—all kinds 404, 400, 30X, etc… Then as those bugs are found and fixed, the web service begins to stabilize. Soon, the only error you get back is the Internal Server (500) error.
So what does the 500 error provide you in its vanilla form? Well, not really all that much. There is no detailed information available. You are left to note the error and then review the logs later. Is this really the best way to do REST error handling? The short answer is no it is not! Fortunately, there is an approach that can be used for REST Error Handling to provide detailed information for the dreaded 500 error.
Let's start with an overview of how the REST Error Handling can be done. First, we will begin with the server side, which is where the error will first occur. In the controller we are going to want to catch the exceptions thrown up by the services, and convert those into an error response, but with some additional information included in the body. The additional information will allow for the error to be re-created on the client side.
A very good question would be so then how will we recreate an error on the client side that happened on the server side. The approach is to pass enough shared information between the client and server to allow that. Each error will be defined by a unique set of identifiers. There will be an identifier for each service, service id. In addition, there will be an identifier for each error within that service, error id. The service ids will need to be unique system wide, and the error ids will need to be unique within the service context.
Then on the client side, it will be necessary to use a custom response handler to be able to receive either the expected response or the error response. The default response handler does not expect the error responses to have additional information, and would fall over. After receiving the response, if it is an error, then the client will need to convert that into an exception, which can then be used in the usual way. The conversion into an exception will then be done by using the service id and error id.
The rest of this article will provide the details of the REST Error Handling approach, but there is also a fully working project checked into GitHub that provides an additional level of details and could be used as a quickstart. For this project I chose to use Java and Spring. In addition, I did all the development in Eclipse.
A good place to start the detailed discussion of REST Error Handling is with the exception classes. These are going to be used by both the client and the server, and they are the secret sauce (if you don’t mind me being a bit cliché). They will need to be packaged as a separate artifact to be available to both the client and the server. Note the packaging used in the sample project was used for the sake of convenience. In a production system, this would be three separate artifacts.
First, there is the BaseException. This is the real nuts and bolts for all the exceptions that will be thrown from the services. Each service will have its own exception class that extends from the BaseException. However, the service specific extensions will really only need to be constructors to create the appropriate exception with the associated service id and error id. All of the real work will happen in the BaseException, and this is by design. That way when catching the exceptions, it will only be necessary to catch the BaseException, as opposed to a never ending list of service specific exceptions.
Next, the REST Error Handling uses two enumerations. The first is the ServiceEnum, which is used to create a map of all the services. Each service will need to be defined in this enumeration. Further, this enumeration has a static method, createServiceException(), which allows for RestError objects to be converted back into exceptions.
The second enumeration or rather set of enumerations is used to create a map of all the service specific errors. In the sample project, there is the ErrorCodeCustomerEnum, which would map the errors from the Customer service. This contains the information that will be used to construct the RestError. It also has a static method, get(), which allows for an error code object to be retrieved from the error id. Internally, this uses a HashMap with the error id as the key.
Also, notice the ErrorCodeEnum interface this will allow the different service specific error enumerations to be placed in the BaseException as a single type and not have to be concerned with exactly which enumeration is being used. This is a not very common usage of interfaces and enumerations, but Java does support it.
Server Side Classes
The REST Error Handling will also use a set of classes on the server side. Naturally, the controller will be the primary class. It will be responsible for accepting requests, invoking the processing and then returning a response. Now, this is where Spring is very helpful. It provides some very helpful built-in functionality. I used the @ExcetionHandler annotation to define a single method to catch and process all of the exceptions being thrown by the services. Notice the use of the BaseException for a much simpler list of exceptions to handle.
The handleCustomException() method will convert the Java exception into a RestError object and return that in the response. It will do this by simply copying the information (service id, error id, message, etc…) from the ErrorCodeEnum. In addition, it needs to configure the HTTP header correctly to ensure it will be processed correctly on the client side, thus it needs to set the content type and status. On the server side, Spring does a lot to make the code pretty simple. However, the client side will be a bit more complex.
Client Side Classes
The client side classes for the REST Error Handling will start with the request being made to the server. There is a base class, BaseWebServiceClientImpl, but this really is only a place to define the common properties: 1) the base URL and 2) an instance of the RestTemplate. The RestTemplate is provided by Spring, and it does the majority of the work for us. It handles sending/receiving the requests/responses for us. Also, different services could use different endpoints, but they would all use the same base URL.
Extended from the BaseWebServiceClientImpl will be all the service specific client classes. In the case of the REST Error Handling sample project, this would be the CustomerClientImpl class. This class (and others like it) would be used to manage the calls to specific service operations. This is done for the sake of maintenance and manageability.
The CustomerClientImpl will first need to access the RestTemplate, and then extend the Spring provided default functionality to be able to receive either the expected response or the RestError response. By default Spring will always catch any HTTP error status and do some internal processing. However, in this case we do not want to do that for the 500 errors, because those will have the RestError in the body. Thus, it is necessary to exclude the 500 errors from the default processing. This is the purpose of the MyResponseErrorHandler class. It extends the Spring class DefaultResponseErrorHandler.
Next, the CustomerClientImpl will need to extend Spring's processing of the actual response. This is done with the two classes, MyRequestCallback and MyResponseExtractor. Both of these classes will need to implement existing Spring interfaces. The customizations that I made were actually pretty simple. What I did was to start with the default classes that are embedded in the Spring RestTemplate, and made slight changes to also process the RestError, if the HTTP status was 500.