Introduction
When developing RESTful applications using Java Backend, sooner or later you’ll face with the necessity of handling HTTP errors in a custom way. For example, frontend may require some kind of generic representation of an HTTP error (usually in JSON format), so that it can easily apply a single logic on it.
In Java Backend world, the most popular technology for handling HTTP requests is Java Servlets. It perfectly suits for handling general HTTP requests as well as static resources. RESTful API, on the other hand, is usually handled by another technology, called JAX-RS.
In this article we are going to implement the custom HTTP error handling mechanism for both Java Servlets and JAX-RS technologies.
Requirements
In general, we want to have the following response for any HTTP error:
{
"code": 404,
"reasonPhrase": "Not Found",
"message": "An HTTP error occurred"
}
where:
code
is an HTTP status codereasonPhrase
is a description of an HTTP errormessage
– any message you may want to display (optional)
First Steps
The first step would be creating the ErrorResponse Java entity:
import java.io.Serializable;
public class ErrorResponse implements Serializable {
private static final long serialVersionUID = 1L;
private final int code;
private final String reasonPhrase;
private String message;
public ErrorResponse(int code, String reasonPhrase) {
this.code = code;
this.reasonPhrase = reasonPhrase;
}
public ErrorResponse(int code, String reasonPhrase, String message) {
this.code = code;
this.reasonPhrase = reasonPhrase;
this.message = message;
}
public int getCode() {
return code;
}
public String getReasonPhrase() {
return reasonPhrase;
}
public String getMessage() {
return message;
}
}
ErrorResponse entity will be serialized into appropriate JSON object by some of the Java JSON library (e.g. Jackson)
Dependencies
Basically, we would need the following dependencies:
dependencies {
providedCompile 'javax.ws.rs:javax.ws.rs-api:2.0.1'
providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
compile 'com.fasterxml.jackson.core:jackson-core:2.5.4'
}
Our Java Backend application is running inside JBOSS container, so we are adding only
javax.*
provided dependencies. The real implementation will be provided by container.
JAX-RS Part
It is very common to add an /api
prefix for all RESTful endpoints in order to distinguish them from other web resources. This can be easily achieved by adding the following configuration into WEB-INF/web.xml
file:
<servlet>
<servlet-name>javax.ws.rs.core.Application</servlet-name>
</servlet>
<servlet-mapping>
<servlet-name>javax.ws.rs.core.Application</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
<context-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<param-value>/api</param-value>
</context-param>
Since JBOSS uses RESTEasy as a JAX-RS implementation, it requires additional resteasy.servlet.mapping.prefix context parameter (pointing to a prefix) to be set
For handling exceptions of any kind, JAX-RS provides javax.ws.rs.ext.ExceptionMapper
interface:
package javax.ws.rs.ext;
import javax.ws.rs.core.Response;
public interface ExceptionMapper<E extends Throwable> {
Response toResponse(E exception);
}
In order to handle any exception, an implementer must do the following:
- Implement the
javax.ws.rs.ext.ExceptionMapper
interface - Mark the implementation class with
@Provider
annotation (fromjavax.ws.rs.ext
package), which will make it discoverable during JAX-RS provider scanning phase
Having an exception mapper for the specific exception type will also be applied for any of its child types.
Say, you want to handle all kinds of HTTP errors (e.g. 404, 500) in the same, but custom way. JAX-RS has separate exceptions for each of the HTTP error status codes (e.g. javax.ws.rs.NotFoundException
for 404), each of which, in turn, extends the parent javax.ws.rs.ClientErrorException
. So, in order to handle all of them, you simply implement an exception mapper for this parent exception:
import javax.ws.rs.ClientErrorException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
@Provider
public class ClientErrorExceptionMapper implements ExceptionMapper<ClientErrorException> {
@Override
public Response toResponse(ClientErrorException exception) {
Response response = exception.getResponse();
Response.StatusType statusType = response.getStatusInfo();
int statusCode = statusType.getStatusCode();
String reasonPhrase = statusType.getReasonPhrase();
String message = "An HTTP error occurred";
ErrorResponse entity = new ErrorResponse(statusCode, reasonPhrase, message);
return Response.ok()
.entity(entity)
.build();
}
}
Now, whenever an HTTP error occurs under /api
endpoint it would be handled by JAX-RS.
Java Servlet Part
Any resource, which is not located under /api
is handled by Java Servlets, and thus requires the same error handling mechanism to be implemented.
For handling HTTP errors, we need to extend an HTTP Servlet and override its service(HttpServletRequest request, HttpServletResponse response)
method. We are interested only in handling non-successful HTTP status codes, so we keep the existing logic for successful HTTP status codes (range between 200 and 299) and apply the HTTP error handling logic for the rest ones:
package com.swupp.pro.apsbau.commons.servlet;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.swupp.pro.apsbau.commons.rest.ErrorResponse;
import com.swupp.pro.apsbau.commons.rest.ExtendedMediaType;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/error-handler-servlet")
public class ErrorHandlerServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
int statusCode = response.getStatus();
if (statusCode >= 200 && statusCode < 299) {
super.service(request, response);
} else {
response.setContentType(ExtendedMediaType.APPLICATION_JSON_UTF_8);
Response.Status status = Response.Status.fromStatusCode(statusCode);
ErrorResponse errorResponse =
new ErrorResponse(statusCode, status.getReasonPhrase());
ObjectMapper objectMapper = new ObjectMapper();
String entity = objectMapper.writeValueAsString(errorResponse);
try (PrintWriter writer = response.getWriter()) {
writer.write(entity);
writer.flush();
}
}
}
}
Here, @WebServlet
annotation is used to make ErrorHandlerServlet
available under the specific URI, which is /error-handler-servlet
. Now, in order to be used for handling any HTTP error, an ErrorHandlerServlet
endpoint must be specified in WEB-INF/web.xml
file:
<error-page>
<location>/error-handler-servlet</location>
</error-page>
That’s it, so now our pretty JSON will be responded for all the errors, which is exactly what we wanted.
If you found this article helpful don’t hesitate to click share buttons below 😉 Take care and read more Java Development articles on our blog.