Taco Steemers

A personal blog.
☼ / ☾

Catching an exception from an annotation on a JAX-RS resource

An exception resulting from an annotation cannot be caught in a regular try / catch block because the result of the annotation is computed before our code is executed.

An exception resulting from an annotation cannot be caught in a regular try / catch block because the result of the annotation is computed before our code is executed.

Example

Take this resource for example:

@Resource
@Path("/example")
public class ExampleResource {

    @GET
    @Path("/")
    @AnnotationThatThrowsException
    public Response getExample() {
        try {
            return Response.status(Response.Status.NO_CONTENT).build();
        } catch (Exception e) {
            return Response.serverError().entity(e.getMessage()).build();
        }
    }
}

Here the code that is run by the annotation processor for AnnotationThatThrowsException, throws an exception. This happens before we actually enter the method. Our try / catch block cannot catch the exception. How do we handle this?

Catching exceptions with a filter

In this example we know about an AccessDeniedException that can be thrown from code that is run because of an annotation. We want to return a 403 Forbidden status code when that exception has been thrown.

Depending on your situation, the exception may already have been caught and rethrown. For that reason we also check any throwable to see if it's cause is an AccessDeniedException.

import javax.enterprise.context.ApplicationScoped;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@ApplicationScoped
@WebFilter(filterName = "ExceptionHandlingFilter", urlPatterns = "/*")
public class ExceptionHandlingFilter implements Filter {

    private static final Logger LOGGER 
        = LoggerFactory.getLogger(ExceptionHandlingFilter.class);

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException {
        try {
            chain.doFilter(request, response);
        } catch (AccessDeniedException e) {
            ((HttpServletResponse) response)
                .sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
        } catch (Throwable t) {
            if (t.getCause() != null && t.getCause() instanceof  AccessDeniedException) {
                    ((HttpServletResponse) response)
                        .sendError(HttpServletResponse.SC_FORBIDDEN, t.getCause().getMessage());
            } else {
                ((HttpServletResponse) response)
                    .sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, t.getMessage());
            }
        }
    }

}

The filter should be registered automatically due to the filter's WebFilter annotation. If for some reason that doesn't work one can try registering it like so:

import javax.inject.Inject;
import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import java.util.EnumSet;
import java.util.Set;

public class FilterInitializer implements ServletContainerInitializer {

    @Inject
    private ExceptionHandlingFilter exceptionHandlingFilter;

    @Override
    public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
        FilterRegistration.Dynamic reg = 
            ctx.addFilter("ExceptionHandlingFilter", exceptionHandlingFilter);
        reg.setAsyncSupported(true);
        reg.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");
    }
}

Exception Mappers

Exception mappers are another way to handle exceptions when using JAX-RS. I believe these are the recommended solution for handling previously uncaught exceptions, and it is worth looking in to them. However, I haven't gotten exception mappers to work for an exception from an annotation.