My project uses Spring Boot + Jersey 2.
I created custom Jackson mapper for JsonParseException, but it didn't get called, standard Jackson JsonParseExceptionMapper used instead.
My custom mapper:
package com.rmn.gfc.common.providers;
import ...
@Provider
public class JsonParseExceptionHandler implements ExceptionMapper<JsonParseException> {
@Override
public Response toResponse(JsonParseException exception) {
return Response
.status(Response.Status.BAD_REQUEST)
// entity ...
.build();
}
}
I register my mapper like this:
@Component
public class OrderServiceResourceConfig extends ResourceConfig {
public OrderServiceResourceConfig() {
packages("com.rmn.gfc.common.providers");
}
}
I am sure that mapper registration is fine because other custom mappers from this package is working, but one for JsonParseException is shadowed by Jersey standard JsonParseExceptionMapper.
How could I override standard Jackson JsonParseExceptionMapper with my implementation?
JacksonFeature
JacksonFeature
registers default exception mappers for Jackson exceptions if the JacksonJaxbJsonProvider
is not registered. And that's exactly what you don't want.
See the relevant parts of the source code:
// Register Jackson. if (!config.isRegistered(JacksonJaxbJsonProvider.class)) { // add the default Jackson exception mappers context.register(JsonParseExceptionMapper.class); context.register(JsonMappingExceptionMapper.class); ... }
JacksonFeature
The Spring Boot's JerseyAutoConfiguration
class will register the JacksonFeature
if it's on the classpath. See the relevant parts of the source code:
@ConditionalOnClass(JacksonFeature.class) @ConditionalOnSingleCandidate(ObjectMapper.class) @Configuration static class JacksonResourceConfigCustomizer { ... @Bean public ResourceConfigCustomizer resourceConfigCustomizer( final ObjectMapper objectMapper) { addJaxbAnnotationIntrospectorIfPresent(objectMapper); return (ResourceConfig config) -> { config.register(JacksonFeature.class); config.register(new ObjectMapperContextResolver(objectMapper), ContextResolver.class); }; } ... }
As a workaround, you can register the JacksonJaxbJsonProvider
and then register your custom exception mappers (or just annotate them with @Provider
to get automatically discovered by Jersey):
@Component
public class OrderServiceResourceConfig extends ResourceConfig {
public OrderServiceResourceConfig() {
packages("com.rmn.gfc.common.providers");
register(JacksonJaxbJsonProvider.class);
// Register other providers
}
}
See what the documentation says about JacksonJaxbJsonProvider
:
JSON content type provider automatically configured to use both Jackson and JAXB annotations (in that order of priority). Otherwise functionally same as
JacksonJsonProvider
.
Alternatively you can get rid of the jersey-media-json-jackson
artifact and use jackson-jaxrs-json-provider
instead. With this, you will get rid of JacksonFeature
and then you can register your own exception mappers.
It was mentioned in this answer.
See the following quote from the JAX-RS 2.1 specification:
4.1.3 Priorities
Application-supplied providers enable developers to extend and customize the JAX-RS runtime. Therefore, an application-supplied provider MUST always be preferred over a pre-packaged one if a single one is required.
Application-supplied providers may be annotated with
@Priority
. If two or more providers are candidates for a certain task, the one with the highest priority is chosen: the highest priority is defined to be the one with the lowest value in this case. That is,@Priority(1)
is higher than@Priority(10)
. If two or more providers are eligible and have identical priorities, one is chosen in an implementation dependent manner.The default priority for all application-supplied providers is
javax.ws.rs.Priorities.USER
. The general rule about priorities is different for filters and interceptors since these providers are collected into chains.
As pointed in Kysil Ivan's answer, write your own exception mapper and give it a high priority, such as 1
. If you use auto discovery, just annotate it with @Provider
and @Priority
.
@Provider
@Priority(1)
public class JsonParseExceptionMapper implements ExceptionMapper<JsonParseException> {
...
}
If you manually register your provider, you can give your provider a binding priority:
@ApplicationPath("/")
public class MyResourceConfig extends ResourceConfig {
public MyResourceConfig() {
register(JsonParseExceptionMapper.class, 1);
}
}
I found solution that is fine for me.
Use javax.annotation.Priority
on your custom mapper to make it override Jackson default mapper, e.g.:
@Provider
@Priority(1)
public class JsonParseExceptionHandler implements ExceptionMapper<JsonParseException> {
// ...
}
OR , if you register JAX-RS components via ResourceConfig, you can specify priority like this:
public class MyResourceConfig extends ResourceConfig {
public MyResourceConfig() {
register(JsonMappingExceptionHandler.class, 1);
register(JsonParseExceptionHandler.class, 1);
// ...
}
}
The lower number the highest priority.javax.ws.rs.Priorities
has some predefined constants for priorities.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With