0

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.

ksl
  • 4,519
  • 11
  • 65
  • 106
  • Because there is already a mapper for `ViolationException` (which is the super class of `ConstraintViolationException`) provided by Jersey to handle constraint violations. Only one mapper can be called per request, to prevent infinite loops – Paul Samsotha Jun 20 '16 at 10:07
  • Do you mean `ValidationException` rather than `ViolationException` by any chance? – ksl Jun 20 '16 at 10:09
  • Probably, I didn't really check – Paul Samsotha Jun 20 '16 at 10:10
  • 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? – ksl Jun 20 '16 at 10:17
  • I'll post that as part of the answer. – Paul Samsotha Jun 20 '16 at 10:18
  • I haven't tried it, and I'm not sure if it will work, but you can try to make a mapper for `WebApplicationException`, and see if the form exception gets caught there. If not then I am not sure if it is possible to handle it. – Paul Samsotha Jun 20 '16 at 10:28
  • That doesn't handle the exception either. – ksl Jun 20 '16 at 10:34

2 Answers2

1

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.

Community
  • 1
  • 1
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
0

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.

Piohen
  • 1,514
  • 15
  • 23