1. Introduction
The RESTEasy Jackson Provider integrates Jackson 3 with Jakarta REST applications running on RESTEasy. It provides JSON serialization and deserialization using Jackson’s high-performance data-binding capabilities.
This provider is a standalone project with its own release cycle, separate from the main RESTEasy project. It uses
Jackson 3 (tools.jackson coordinates), not Jackson 2 (com.fasterxml.jackson). For the older Jackson 2
integration, see the org.jboss.resteasy:resteasy-jackson2-provider artifact documented in the
main RESTEasy documentation.
Features include:
-
JSON serialization and deserialization with Jackson 3
-
Support for
application/json,application/*+json, andtext/jsonmedia types -
JSON Patch (RFC 6902) support
-
JSON Merge Patch (RFC 7396) support
-
Polymorphic type handling with allow-list validation
-
Custom
JsonMapperconfiguration viaContextResolver -
RESTEasy tracing integration
1.1. Version Information
This guide is for RESTEasy Jackson Provider version 1.0.0.Beta1.
-
For the main RESTEasy documentation (Jakarta REST implementation), see docs.resteasy.dev.
2. Getting Started
2.1. Maven Dependencies
Add the RESTEasy Jackson Provider to the project:
<dependency>
<groupId>dev.resteasy.providers</groupId>
<artifactId>resteasy-jackson-provider</artifactId>
<version>1.0.0.Beta1</version>
</dependency>
The provider auto-registers via the jakarta.ws.rs.core.Feature service loader mechanism. No additional
configuration is needed — once the dependency is on the classpath, JSON support is available.
2.2. Basic JSON Endpoints
The provider automatically handles serialization and deserialization of Java objects to and from JSON.
Reading JSON:
@Path("/customers")
public class CustomerResource {
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response createCustomer(Customer customer) {
// customer is deserialized from the JSON request body
save(customer);
return Response.status(Response.Status.CREATED).entity(customer).build();
}
@GET
@Path("/{id}")
@Produces(MediaType.APPLICATION_JSON)
public Customer getCustomer(@PathParam("id") String id) {
// the returned object is serialized to JSON
return findCustomer(id);
}
}
2.3. Supported Media Types
The provider handles the following media types:
-
application/json -
application/*+json(vendor-specific JSON media types) -
text/json
The application/*+json wildcard allows the use of custom JSON-based media types:
@GET
@Produces("application/vnd.customer+json")
public Customer getCustomer() {
return findCustomer();
}
2.4. Migration from resteasy-jackson2-provider
This provider uses Jackson 3, which has different Maven coordinates and package names from Jackson 2.
Jackson 2 (resteasy-jackson2-provider) |
Jackson 3 (resteasy-jackson-provider) |
|---|---|
|
|
|
|
Group ID: |
Group ID: |
|
|
|
|
3. JSON Patch and JSON Merge Patch
The provider includes support for modifying resources using JSON Patch
(RFC 6902) and JSON Merge Patch
(RFC 7396). Both use the
dev.resteasy.providers.jackson.ObjectPatch interface as the parameter type in resource methods. The media
type on @Consumes determines which patch format is used.
3.1. JSON Patch (RFC 6902)
JSON Patch expresses a sequence of operations (add, remove, replace, move, copy, test) to apply to a JSON document.
Use the media type application/json-patch+json.
@PATCH
@Path("/customer/{id}")
@Consumes("application/json-patch+json")
@Produces(MediaType.APPLICATION_JSON)
public Customer patchCustomer(@PathParam("id") String id, ObjectPatch patch) {
Customer customer = findCustomer(id);
return patch.apply(customer);
}
Example request body:
[
{"op": "replace", "path": "/name", "value": "New Name"},
{"op": "add", "path": "/tags/-", "value": "new-tag"},
{"op": "remove", "path": "/temporary"}
]
The supported operations are:
| Operation | Description |
|---|---|
|
Add a value to an object or array |
|
Remove a value from an object or array |
|
Replace an existing value |
|
Move a value from one path to another |
|
Copy a value from one path to another |
|
Test that a value at a path equals the specified value |
Patch application is atomic — a deep copy of the target object is made before applying operations. If any operation fails, the original object is unchanged.
3.2. JSON Merge Patch (RFC 7396)
JSON Merge Patch provides a simpler patching mechanism. Use the media type application/merge-patch+json.
@PATCH
@Path("/customer/{id}")
@Consumes("application/merge-patch+json")
@Produces(MediaType.APPLICATION_JSON)
public Customer mergePatchCustomer(@PathParam("id") String id, ObjectPatch patch) {
Customer customer = findCustomer(id);
return patch.apply(customer);
}
Example request body:
{
"name": "Updated Name",
"email": null
}
Merge patch semantics:
-
Setting a field to a value updates that field
-
Setting a field to
nullremoves it -
Object values are merged recursively
-
Array values are replaced entirely (not merged element-by-element)
4. Configuration
4.1. Custom JsonMapper
To customize Jackson’s behavior, register a ContextResolver<JsonMapper> as a Jakarta REST provider. The provider
looks up the JsonMapper via this resolver before falling back to a default mapper.
@Provider
public class CustomJsonMapperProvider implements ContextResolver<JsonMapper> {
@Override
public JsonMapper getContext(final Class<?> type) {
return JsonMapper.builder()
.enable(SerializationFeature.INDENT_OUTPUT)
.build();
}
}
The ContextResolver receives the type being serialized or deserialized, allowing different mapper configurations
per type if needed.
4.2. Polymorphic Type Validation
For security, the provider installs a polymorphic type validator by default. This controls which classes are allowed during polymorphic deserialization, preventing deserialization attacks.
The allow-list is configured via system properties or RESTEasy configuration properties:
| Property | Description |
|---|---|
|
Comma-separated prefixes for allowed base types. Use |
|
Comma-separated prefixes for allowed subtypes. Use |
Example configuration allowing types in the com.example package:
dev.resteasy.jackson.deserialization.allowlist.allowIfSubType=com.example
4.3. Exception Handling
The provider includes a JsonProcessingExceptionMapper that maps Jackson’s JacksonException to an HTTP 400
(Bad Request) response. The response body contains a generic error message. Exception details are logged at ERROR
level but are not exposed in the response to avoid leaking internal information.
4.4. ObjectWriter and ObjectReader Modifiers
The provider supports customizing Jackson’s ObjectWriter and ObjectReader at request time via modifier injection.
This allows you to alter serialization or deserialization behavior on a per-request basis: for example, enabling
indented output, adding response headers, or enforcing strict deserialization rules.
To inject a modifier, register a ContextResolver<ObjectWriterModifier> or ContextResolver<ObjectReaderModifier> as
a Jakarta REST provider. The provider looks up the modifier via this resolver before each write or read operation.
4.4.1. ObjectWriterModifier
An ObjectWriterModifier can modify the ObjectWriter and the response headers before serialization.
public class IndentingWriterModifier extends ObjectWriterModifier {
@Override
public ObjectWriter modify(final EndpointConfigBase<?> endpoint,
final MultivaluedMap<String, Object> responseHeaders,
final Object valueToWrite,
final ObjectWriter w) throws JacksonException {
return w.with(SerializationFeature.INDENT_OUTPUT);
}
}
Register the modifier via a ContextResolver:
@Provider
public class WriterModifierResolver implements ContextResolver<ObjectWriterModifier> {
@Override
public ObjectWriterModifier getContext(final Class<?> type) {
return new IndentingWriterModifier();
}
}
4.4.2. ObjectReaderModifier
An ObjectReaderModifier can modify the ObjectReader and the JsonParser before deserialization.
public class StrictReaderModifier extends ObjectReaderModifier {
@Override
public ObjectReader modify(final EndpointConfigBase<?> endpoint,
final MultivaluedMap<String, String> httpHeaders,
final JavaType resultType,
final ObjectReader r,
final JsonParser p) throws JacksonException {
return r.with(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
}
}
Register the modifier via a ContextResolver:
@Provider
public class ReaderModifierResolver implements ContextResolver<ObjectReaderModifier> {
@Override
public ObjectReaderModifier getContext(final Class<?> type) {
return new StrictReaderModifier();
}
}
The ContextResolver receives the type being serialized or deserialized, allowing different modifier behavior per type
if needed.
Jackson also provides ObjectWriterInjector and ObjectReaderInjector, which use a ThreadLocal to pass a
modifier from a filter into the provider. These work when the filter and the resource endpoint execute on the same
thread, but are unreliable in Jakarta EE containers where the request may be dispatched across threads. The
ContextResolver approach is recommended as it is container-safe and does not depend on thread affinity. If both a
ThreadLocal modifier and a ContextResolver are present, the ThreadLocal modifier takes precedence and the
ContextResolver is not consulted.
|
4.5. Tracing Integration
The provider includes a JacksonJsonFormatRESTEasyTracingInfo implementation that provides JSON-formatted tracing
output when RESTEasy’s tracing feature is enabled. This is auto-registered via the
META-INF/services/org.jboss.resteasy.tracing.api.RESTEasyTracingInfo service loader mechanism and requires no
additional configuration.