Search code examples
javajerseyjax-rsjersey-2.0

Jersey - Custom exception mapper not invoked on invalid POST request


I'm using Jersey 2.22.1 to implement a REST API.

I have created a custom exception mapper to map a RuntimeException to a particular HTTP response code of my choosing.

@Provider
public class MyExceptionMapper implements ExceptionMapper<RuntimeException>
{
    @Override
    public Response toResponse(RuntimeException exception)
    {
        ....
    }
}

My resource class is configured to take a number of parameters for a POST request.

@Path("rest/api/1.0/test")
public class MyResource
{
    @NotNull(message = "Missing parameter 'param1'")
    @FormParam("param1")
    private String m_param1;

    @NotNull(message = "Missing parameter 'param2'")
    @FormParam("param2")
    private String m_param2;

    @POST
    @Produces(MediaType.TEXT_PLAIN)
    @Consumes("application/x-www-form-urlencoded")
    public Response test(String body)
    {
        ...
    }
}

When I send a POST request without any parameters:

curl -X POST http://192.168.0.2:9989/myApp/rest/api/1.0/test

I'm expecting my custom exception mapper to be invoked, but it isn't. Instead I get a bunch of IllegalStateException.

WARNING: The following warnings have been detected: WARNING: Unknown HK2 failure detected:
MultiException stack 1 of 6
java.lang.IllegalStateException: The @FormParam is utilized when the content type of the request entity is not application/x-www-form-urlencoded
    at org.glassfish.jersey.server.internal.inject.FormParamValueFactoryProvider$FormParamValueFactory.ensureValidRequest(FormParamValueFactoryProvider.java:183)
    at org.glassfish.jersey.server.internal.inject.FormParamValueFactoryProvider$FormParamValueFactory.getForm(FormParamValueFactoryProvider.java:167)
    at org.glassfish.jersey.server.internal.inject.FormParamValueFactoryProvider$FormParamValueFactory.provide(FormParamValueFactoryProvider.java:118)
    at org.glassfish.jersey.server.internal.inject.ParamInjectionResolver.resolve(ParamInjectionResolver.java:134)
    at org.jvnet.hk2.internal.ClazzCreator.resolve(ClazzCreator.java:211)
    at org.jvnet.hk2.internal.ClazzCreator.resolveAllDependencies(ClazzCreator.java:234)
    at org.jvnet.hk2.internal.ClazzCreator.create(ClazzCreator.java:357)
    at org.jvnet.hk2.internal.SystemDescriptor.create(SystemDescriptor.java:471)
    at org.glassfish.jersey.process.internal.RequestScope.findOrCreate(RequestScope.java:162)
    at org.jvnet.hk2.internal.Utilities.createService(Utilities.java:2072)
    at org.jvnet.hk2.internal.ServiceLocatorImpl.internalGetService(ServiceLocatorImpl.java:767)
    at org.jvnet.hk2.internal.ServiceLocatorImpl.getService(ServiceLocatorImpl.java:706)
    at org.glassfish.jersey.internal.inject.Injections.getOrCreate(Injections.java:172)
    at org.glassfish.jersey.server.model.MethodHandler$ClassBasedMethodHandler.getInstance(MethodHandler.java:284)
    at org.glassfish.jersey.server.internal.routing.PushMethodHandlerRouter.apply(PushMethodHandlerRouter.java:74)
    at org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:109)
    at org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:112)
    at org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:112)
    at org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:112)
    at org.glassfish.jersey.server.internal.routing.RoutingStage.apply(RoutingStage.java:92)
    at org.glassfish.jersey.server.internal.routing.RoutingStage.apply(RoutingStage.java:61)
    at org.glassfish.jersey.process.internal.Stages.process(Stages.java:197)
    at org.glassfish.jersey.server.ServerRuntime$2.run(ServerRuntime.java:318)
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:271)
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:267)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:315)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:297)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:267)
    at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:317)
    at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:305)
    at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:1154)
    at org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpContainer.service(GrizzlyHttpContainer.java:384)
    at org.glassfish.grizzly.http.server.HttpHandler$1.run(HttpHandler.java:224)
    at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:591)
    at org.glassfish.grizzly.threadpool.AbstractThreadPool$Worker.run(AbstractThreadPool.java:571)
    at java.lang.Thread.run(Thread.java:745)

Can anyone explain why my custom exception mapper is not being invoked?

Note that if I throw a RuntimeException in my resource method and then send a valid POST request, then the exception mapper is invoked. So I know if works to some extent.

UPDATE

I have discovered that if I add the following to the HTTP request:

-H "Content-Type: application/x-www-form-urlencoded"

then my ConstraintViolationExceptionMapper is invoked.

@Provider
public class ConstraintViolationExceptionMapper implements ExceptionMapper<ConstraintViolationException>
{
    ...
}

But if I remove the ConstraintViolationExceptionMapper, my custom exception mapper is still not invoked.


Solution

  • Before Update Error

    Following the JAX-RS spec, for @FormParam and other @XxxParams, when an exception occurs during the parsing of the param, Jersey throws an exception that is already mapped. This is to follow the spec in order to return the correct response status. So we never have a chance to handle it.

    Problem with Update

    Jersey already has a mapper for ValidationException which is a super class of ConstraintViolationException. So when you provider the mapper for the ConstrainViolationException, it is more specific than the one for ValidationException, so it gets called.

    Even if I provide a mapper for ValidationException, it is not invoked when I send a request without a header. Do you know why that is?

    Because the param parsing occurs before the validation.