10

In one of my projects I've already upgraded Jersey from version 2.14 to 2.23. But I'm struggling many hours with one problem. My project defines its own ExceptionMapper for a ValidationException, but unfortunately Jersey already has a built-in exception mapper for this exception and I cannot override it.

I have registered correctly (I checked it) my own mapper which is presented below:

@Provider
public class ValidationExceptionMapper implements 
         ExceptionMapper<ValidationException> {

    @Override
    public Response toResponse(ValidationException exception) {
        return Response.status(Status.BAD_REQUEST).build();
    }
}

but it is never being called. Jersey always pick up the org.glassfish.jersey.server.validation.internal.ValidationExceptionMapper. I've also tried to use @Priority annotation for my custom mapper, but unfortunately Jersey doesn't take it into account.

So what is going on? It worked perfectly fine in the previous Jersey version, so it seems to be a regression bug.

I give up. Any clues?

G. Demecki
  • 10,145
  • 3
  • 58
  • 58
  • You could use `ConstraintViolationException` (which is the actual type of the validation exceptions) as a work around. It is more specific than `ValidationException`, so it will take precedence. I never figured out how to disable that one mapper, without disabling _all_ meta-inf providers (not a fun solution). My work around was to simply use a mapper for `ConstraintViolationException` – Paul Samsotha Aug 01 '16 at 09:01
  • Thanks, I know that, but this doesn't solve the issue at all. Because application has to respond to `ValidationException`s. – G. Demecki Aug 01 '16 at 09:07
  • `ConstraintViolationException` extends `ValidationException`. The exception type thrown with the bean validation in Jersey will always be a `ConstraintViolationException`. It works for `ValidationException`, because super type mappers can handle sub-type exceptions, is there is no mapper for the sub-type – Paul Samsotha Aug 01 '16 at 09:10
  • 2
    It would be cool if you submitted a bug at https://java.net/jira/browse/JERSEY for this regression. If you added a simple reproducible case it would be even better. Thanks! – Stepan Vavra Aug 02 '16 at 07:23
  • @peeskillet it turned out to be a regression bug. Pity that you didn't report it earlier. Maybe it would be fixed till now. Regards anyway. – G. Demecki Aug 11 '16 at 14:10

4 Answers4

7

It really turned out to be a regression bug in Jersey, introduced in January 2015.

Bug is related with two Jersey's extensions: for Weld and bean validation. Because without Weld container started, my custom ValidationExceptionMapper mapper takes precedence over the built-in one provided by the jersey-bean-validation module, so my goal is achieved.

I've filled a bug report under JERSEY-3153, later moved as the issue #3425.

To be honest, I'm never ever going to use Weld + Jersey again... I'm so tired with this combination. Through the last two years I've encountered around 10 bugs already. I'm really tired.

Anyway, I hope it will help somebody.

UPDATE: As @Justin Jose noticed in the comments below, there is also another workaround for the mentioned bug. We can use HK2 bindings, to override the problematic built-in mapper:

register(new AbstractBinder() {
    @Override
    protected void configure() {
        bind(my.custom.ValidationExceptionMapper.class).to(ExceptionMapper.class)
               .in(Singleton.class);
    }
});
G. Demecki
  • 10,145
  • 3
  • 58
  • 58
  • 2
    2k19, Jersey 2.27 - this bug is still here... And this workaround still works, thanks! Btw, new bug home: https://github.com/eclipse-ee4j/jersey/issues/3425 – Ruslan Stelmachenko Jan 15 '19 at 16:09
  • 1
    Also please note that while this solution works, I discovered that it sometimes doesn't. :-) I think, the priority of binding standard and custom `ExceptionMapper` is random. I added `.ranked(10‌​);` at the end of binding code as mentioned in other answer and as I see, it works stable now (custom mapper takes the priority). – Ruslan Stelmachenko Jan 16 '19 at 14:21
5

Jersey's built-in ValidationExceptionMapper is registered via ValidationFeature. Probably, replacing Jersey's ValidationFeature with your own version can do the trick. It can be done as follows.

Firstly, disable auto-discoverable ValidationFeature

property(ServerProperties.BV_FEATURE_DISABLE, true);

Next step is to register a clone of Jersey's validation feature

public static class ValidationFeatureClone implements Feature {

    @Override
    public boolean configure(FeatureContext context) {
        context.register(new ValidationBinder());
        context.register(NewValidationExceptionMapper.class);
        context.register(ValidationErrorMessageBodyWriter.class);
        return true;
    }
}

In the clone, you should specify your new ExceptionMapper.

Finally, register your new Feature

register(ValidationFeatureClone.class)

UPDATE:

From Jersey 2.20 onwards, default ValidationExceptionMapper can be overwritten using HK2 binding as shown below.

register(new AbstractBinder() {
    @Override
    protected void configure() {

       bind(NewValidationExceptionMapper.class).to(ExceptionMapper.class)
           .in(Singleton.class).ranked(10‌​);
    }
});
Zounadire
  • 1,496
  • 2
  • 18
  • 38
Justin Jose
  • 2,121
  • 1
  • 16
  • 15
  • Please double check your solution. I've checked it a minute ago with Jersey 2.23.1 and it doesn't solve the original problem at all. Beside that `ValidationErrorMessageBodyWriter` is not publicly available class. – G. Demecki Mar 03 '17 at 08:18
  • I am using Jersey 2.19 in our application and the solution has worked for us. It looks like from 2.20 onwards `ValidationErrorMessageBodyWriter` is changed to a package-private class. I didn't notice your mention of 2.23 in your original question. Can you please try creating a clone of `ValidationErrorMessageBodyWriter`? – Justin Jose Mar 03 '17 at 08:59
  • It looks like in the latest versions a custom `ValidationExceptionMapper` can be registered using HK2 binding and there is no need to register a clone of Validation feature as I mentioned in my original answer. Can you please try adding `register(new AbstractBinder() { @Override protected void configure() { bind(NewValidationExceptionMapper.class).to(ExceptionMapper.class).in(Singleton.class).ranked(10); } });` – Justin Jose Mar 03 '17 at 09:22
  • Your workaround with using HK2 works. Thanks Justin. I'll update the section "Known workarounds" in the [JERSEY-3153](https://java.net/jira/browse/JERSEY-3153). Please update also your answer above :) – G. Demecki Mar 06 '17 at 08:12
  • see also https://stackoverflow.com/a/70789779/1688570 – TouDick Jan 20 '22 at 16:21
0

I found a way to get it to work with newer Jersey releases again, which I also posted under your bug report.

One needs to build Jersey locally with the changed code, specifically the jersey-bean-validation artifact.

Locate org.glassfish.jersey.server.validation.internal.ValidationBinder and comment out the following two lines in configure():

bind(ValidationExceptionMapper.class).to(ExceptionMapper.class).in(Singleton.class);
bind(ValidationErrorMessageBodyWriter.class).to(MessageBodyWriter.class).in(Singleton.class);

It's kind of ironic that the source code comment above those lines says that they're supposed to allow users to register their own providers.

Hein Blöd
  • 1,553
  • 1
  • 18
  • 25
0

Unfortunately the bug still exists and cause headaches... In my case easiest solution was providing custom ExceptionMapper specifically for ConstraintViolationException.

public class CVExceptionMapper implements ExceptionMapper<ConstraintViolationException> {
   @Override
   public Response toResponse(final Throwable t) {
   ...
   }
}

and then registering it as always with: context.register(CVExceptionMapper.class);

TouDick
  • 1,262
  • 12
  • 18