Handling errors in a REST way is seemingly simple enough: upon requesting a resource, when an error occurs, a proper status code and a body that contains a parseable message and using the content-type of the request should be returned.
The default error pages in Tomcat are ugly. Not only they expose too much of the server internals, they are only HTML formatted and making them a poor choice if a RESTful web service is deployed in that Tomcat container. Substituting them to simple static pages is still no enough since I want a dynamic response containing error information.
Here’s how to do it in 3 simple steps:
Step 1
In web.xml
<servlet-mapping> <servlet-name>jsp</servlet-name> <url-pattern>/error.jsp<url-pattern> </servlet-mapping> <!-- other servlet mappings --&>& <error-page> <error-code>404</error-code> <location>/error.jsp</location> </error-page> <error-page> <error-code>400</error-code> <location>/error.jsp</location> </error-page> ... <error-page> <exception-type>java.lang.Throwable</exception-type> <location>/error.jsp</location> </error-page>
The servlet called jsp comes out-of the box with the Tomcat container and is the one responsible for processing JSPs . Make sure this mapping comes first, especially if you have a catch-all url-pattern that should map all other URLs to your webapp.
Step 2
Create a file called errors.jsp in the root of your web application:
<%@ page contentType="application/json" pageEncoding="UTF-8"%> { status:<%=request.getAttribute("javax.servlet.error.status_code") %>, reason:<%=request.getAttribute("javax.servlet.error.message") %> }
Step 3
Hook the exception handling using @ExceptionHandler:
@Controller public class SomeController { ... @ExceptionHandler(BusinessException.class, ValidationException.class, ...) public void handleApplicationExceptions(Throwable exception, HttpServletResponse response) { String formattedErrorForFrontEnd = formatErrMessage(exception); if(exception instanceof BusinessException) { response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, formattedErrorForFrontEnd); } else if (exception instanceof ValidationException) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, formattedErrorForFrontEnd); } ... } //other @ExceptionHandlers skipped private String formatErrMessage(Throwable exception) { //skipped } }
I used @ExceptionHandler to map between application exceptions and error codes. You can apply message formatting as well if the default messages are not sufficient. Also, consider having an @ExceptionHandler(Throwable.class) for the “all other exceptions” case.
As you can see, there is no need to write another Spring Controller that handles exceptional paths. The error-page mappings in web.xml, a 2 line JSP and the standard @ExceptionHandler combination was sufficient.