Java Backend Custom Error Handling

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 code
  • reasonPhrase is a description of an HTTP error
  • message – 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 (from javax.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.

Vladyslav Baidak - Scalified

Vladyslav Baidak

Backend Engineer at Scalified