How to handle Exceptions in JAX-RS applications

This article will teach you how to handle Exceptions properly in RESTful Web services using JAX-RS API and some advanced options which are available with RESTEasy and Quarkus runtime.

Overview of REST Exceptions

As for every Java classes, REST Endpoints can throw in their code both checked Exceptions (i.e. classes extending java.lang.Exception) and unchecked (i.e. classes extending java.lang.RuntimeException).

These exceptions are handled by the JAX-RS runtime which will convert the Exception in an HTTP response. How does the process work?

  • Firstly, the Web container searches for an Exception Mapper. If found, the Exception Mapper will handle the error condition and return the Response code.
  • If no Exception Mapper is found, the Exception is propagated into the Web container that will handle it.

A JAX-RS Container provides out of the box the javax.ws.rs.WebApplicationException is able to return an error response code to Clients, without the need to write an Exception Mapper.

Here is a code snippet which shows how to throw a WebApplicationException to return the SC_UNAUTHORIZED Response code (401) indicating that the request requires HTTP authentication:

    @GET
    @Produces("text/plain")
    public String get() {
        if (!securityContext.isUserInRole("admin")) {
            throw new WebApplicationException(Response.serverError().status(HttpResponseCodes.SC_UNAUTHORIZED)
                    .entity("User " + securityContext.getUserPrincipal().getName() + " is not authorized").build());
        }
        return "Good user " + securityContext.getUserPrincipal().getName();
    }

Coding a sample Exception Mapper

If you need to implement some custom logic within your Exception flow, you can add your own Exception Mapper class extending javax.ws.rs.ext.ExceptionMapper.

Let’s see an example. In the following REST Endpoint, we want to handle a null Query parameter with a custom Exception named MyException:

@Path("/hello")
public class SimpleRESTService {

	@GET
	public String hello(@QueryParam("id") final Integer id) {
		if (id == null) throw new MyException("You need to pass an Id!");
		
		return "Hello " +id;
	}

 
}

Next, we will code the following Exception class:

import javax.ws.rs.WebApplicationException;

public class MyException extends WebApplicationException {

	public MyException() {
		super();
	}

	public MyException(String message) {
		super("MyException Error: " +message);
	}

}

Finally, we need to register an ExceptionMapper class, implementing the toResponse method which ultimately returns the Status code:

import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

@Provider
public class MyExceptionMapper  implements ExceptionMapper<MyException> {
	 @Override
	    public Response toResponse(MyException exception) 
	    {
		    System.out.println(exception);
	        return Response.status(Status.BAD_REQUEST).entity(exception.getMessage()).build();  
	    }
}

Testing the REST Service

Next, we can test our Exception Mapper. We will query for the “hello” endpoint without the required QueryParam:

how to handle exception rest service

Mapping Exceptions in your configuration

If you are running WildFly or JBoss EAP, there are extra options available to map your Exceptions. As a matter of fact, you can provide the list of Provider classes through the web.xml configuration file.

Here is an example web.xml which matches with our demo:

<context-param>
        <param-name>resteasy.providers</param-name>
        <param-value>com.mastertheboss.jaxrs.service.MyExceptionMapper</param-value>        
</context-param>

Finally, if you want to use your own Exception Mapper for multiple Web applications, you can set the property resteasy-providers in the jaxrs subsystem. Connect to the CLI and run the following command:

/subsystem=jaxrs/:write-attribute(name=resteasy-providers,value=["com.mastertheboss.jaxrs.service.MyExceptionMapper"])

Handling Exceptions in Reactive REST Services

If you are running Reactive REST Services (available in Quarkus) then the above process is simplified.

As a matter of fact, you can include directly in your Endpoint an Exception class which extends org.jboss.resteasy.reactive.server.ServerExceptionMapper.

Methods in a JAX-RS class annotated with this annotation take precedence over the global javax.ws.rs.ext.ExceptionMapper classes we have covered so far.

Example:

@Path("image")
public class Endpoint {

    @Inject
    DemoService service;

    @ServerExceptionMapper
    public RestResponse<String> mapException(MissingImageException x) {
        return RestResponse.status(Response.Status.NOT_FOUND, "Unknown image: " + x.name);
    }

    @GET
    public String findFromage(String fromage) {
        if(fromage == null)
            // send a 400
            throw new MissingImageException();
        return service.findImage(fromage);
    }
}

As you can see, the advantage of this approach is that you can use an Exception Mapper which is specific for a JAX-RS Endpoint.

On the other hand, if the annotation is placed on a method that is not a JAX-RS Resource class, the method handles exceptions globally, like the ExceptionMapper class.

Source code and recommended readings

To learn more about Reactive JAX-RS Services, we recommend checking this article: Reactive REST with Quarkus made easy

To learn how to handle REST Exceptions in Spring Boot applications, please check this article: http://www.masterspringboot.com/web/rest-services/exception-handling-in-spring-boot-applications/

Source code for this tutorial: https://github.com/fmarchioni/mastertheboss/tree/master/jax-rs/exception-mapper

Found the article helpful? if so please follow us on Socials