ksl
ksl

Reputation: 4699

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.

Upvotes: 0

Views: 2170

Answers (2)

Piohen
Piohen

Reputation: 1530

The accepted answer seems to be not correct (any more).

So we never have a chance to handle it.

Isn't true now. Check What seems to be the correct solution in https://stackoverflow.com/a/45478604/1271372.

I followed that, implemented ExceptionMapper<SomeParseException> and registered it in the resource config register(ExceptionMapperImplementation.class, 1) before JacksonFeature and it works like a charm. Note the priority 1.

Of course, this should return code 400. I had to customise the response body to be JSON, not plain text.

Upvotes: 0

Paul Samsotha
Paul Samsotha

Reputation: 208964

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.

Upvotes: 1

Related Questions