Introduction
While the upgrade to Jakarta RS 3 is part of the Jakarta EE 10 one, it has its own section because the implementation (Jersey), leveraged by Nuxeo WebEngine, has more changes.
Nuxeo WebEngine has undergone breaking changes to benefit from the new Jakarta RS additions.
You must first follow the How to upgrade to Jakarta EE 10 page.
JAX-RS occurrences have been replaced by REST within the Nuxeo Server codebase.
Maven Dependency
In addition to the Jakarta RS Specification and Implementation dependencies to change, you will need to update:
nuxeo-webengine-jaxrs
tonuxeo-webengine-rest
nuxeo-importer-jaxrs
tonuxeo-importer-rest
nuxeo-target-platforms-jaxrs
tonuxeo-target-platforms-rest
nuxeo-template-rendering-jaxrs
tonuxeo-template-rendering-rest
Java Code
One of the biggest change in the Jersey Implementation is the total rework of their Dependency Injection engine.
It is now more extendable, and better fits with the direction of Jakarta RS specification to support jakarta.inject-api
annotations, such as @Singleton
which is currently used.
Package
The following Nuxeo packages have changed in order to remove JAX-RS occurrences:
org.nuxeo.ecm.webengine.jaxrs
toorg.nuxeo.ecm.webengine.rest
org.nuxeo.ecm.restapi.server.jaxrs
toorg.nuxeo.ecm.restapi.server
org.nuxeo.ecm.automation.jaxrs.io
toorg.nuxeo.ecm.automation.io.rest
org.nuxeo.ecm.automation.server.jaxrs
toorg.nuxeo.ecm.automation.server.rest
org.nuxeo.targetplatforms.jaxrs
toorg.nuxeo.targetplatforms.rest
org.nuxeo.template.jaxrs
toorg.nuxeo.template.rest
org.nuxeo.ecm.restapi.server.jaxrs
must be updated to
org.nuxeo.ecm.restapi.server
to keep the Package Scan working.
Nuxeo Component
The following Nuxeo bundle names have changed in order to remove JAX-RS occurrences:
org.nuxeo.ecm.webengine.jaxrs
toorg.nuxeo.ecm.webengine.rest
org.nuxeo.targetplatforms.jaxrs
toorg.nuxeo.targetplatforms.rest
org.nuxeo.template.manager.jaxrs
toorg.nuxeo.template.manager.rest
@Deploy
annotation in test classes must be updated accordingly.
Message Body Writer Selection Algorithm
The MessageBodyWriter
is retrieved differently in this upgrade as explained in the Jakarta Documentation.
The impact on the WebEngine application code is that Jersey now makes more use of Java Reflection to retrieve the right
MessageBodyWriter
, and so declaring the generic Object type may cause errors.
For example, following method in a resource/WebObject:
@GET
public Object doGet() {
return new DocumentModelImpl(...);
}
May produce this kind of errors:
Caused by: org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyWriter not found for media type=application/json, type=class org.nuxeo.ecm.core.api.impl.DocumentModelImpl, genericType=class java.lang.Object.
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorExecutor.java:248) ~[jersey-common-2.27.jar:?]
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163) ~[jersey-common-2.27.jar:?]
at org.glassfish.jersey.server.internal.JsonWithPaddingInterceptor.aroundWriteTo(JsonWithPaddingInterceptor.java:109) ~[jersey-server-2.27.jar:?]
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:163) ~[jersey-common-2.27.jar:?]
at org.glassfish.jersey.server.internal.MappableExceptionWrapperInterceptor.aroundWriteTo(MappableExceptionWrapperInterceptor.java:85) ~[jersey-server-2.27.jar:?]
... 66 more
Caused by: javax.ws.rs.NotAcceptableException: HTTP 406 Not Acceptable
at org.glassfish.jersey.server.internal.routing.MethodSelectingRouter.getMethodRouter(MethodSelectingRouter.java:471)
at org.glassfish.jersey.server.internal.routing.MethodSelectingRouter.access$000(MethodSelectingRouter.java:73)
at org.glassfish.jersey.server.internal.routing.MethodSelectingRouter$4.apply(MethodSelectingRouter.java:673)
at org.glassfish.jersey.server.internal.routing.MethodSelectingRouter.apply(MethodSelectingRouter.java:304)
at org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:86)
at org.glassfish.jersey.server.internal.routing.RoutingStage._apply(RoutingStage.java:89)
In such cases you should declare the concrete type: DocoumentModel
.
If the method returns different incompatible types, you can replace Object
by jakarta.ws.rs.core.Response
(and adapt
the code to return a Response
with help of ResponseBuilder
).
This ensures that Jersey does not rely on method reflection introspection.
WebContext Managed by A Jakarta Provider
Previously, the WebContext
was initialized with a Servlet Filter
, and filled later on with its HttpHeaders
and UriInfo
.
Now in LTS 2025, the WebContext
is initialiazed by a Jakarta Provider
and is immutable. This allows to access the context
in any Jakarta objects with the help of the @Context
annotation.
If you are overriding the WebEngineModule#getClasses
without getting the result from the super class, you must add the
WebContextProvider
to the classes:
import org.nuxeo.ecm.webengine.app.WebContextProvider;
public class MyModule extends WebEngineModule {
@Override
public Set<Class<?>> getClasses() {
var result = new HashSet<Class<?>>();
result.add(WebContextProvider.class);
return result;
}
}
Otherwise you may have such exception:
Caused by: org.glassfish.hk2.api.MultiException: A MultiException has 2 exceptions. They are:
1. java.lang.NullPointerException: Cannot invoke "org.nuxeo.ecm.webengine.model.WebContext.getModule()" because "context" is null
2. java.lang.IllegalStateException: Unable to perform operation: method inject on org.nuxeo.modyle.rest.MyModuleRoot
at org.jvnet.hk2.internal.ClazzCreator.create(ClazzCreator.java:368) ~[hk2-locator-2.6.1.jar:?]
at org.jvnet.hk2.internal.SystemDescriptor.create(SystemDescriptor.java:463) ~[hk2-locator-2.6.1.jar:?]
at org.glassfish.jersey.inject.hk2.RequestContext.findOrCreate(RequestContext.java:59) ~[jersey-hk2-2.43.jar:?]
...
Caused by: java.lang.NullPointerException: Cannot invoke "org.nuxeo.ecm.webengine.model.WebContext.getModule()" because "context" is null
at org.nuxeo.ecm.webengine.model.impl.ModuleRoot.setContext(ModuleRoot.java:55) ~[classes/:?]
...
Form APIs have been removed from WebContext
The Form APIs have been removed from the WebContext class in order to leverage the Jersey provided feature.
If you were using the WebContext#getForm()
method, you must upgrade your code to leverage method parameters.
For example, the following code:
@POST
public Response doPost() {
DocumentModel doc = getTarget().getAdapter(DocumentModel.class);
FormData form = ctx.getForm();
form.fillDocument(doc);
String xpath = ctx.getForm().getString(FormData.PROPERTY);
Blob blob = form.getFirstBlob();
if (xpath == null || blob == null) {
throw new RuntimeException("Illegal parameters");
}
...
}
Should be updated to:
import javax.ws.rs.FormParam;
import javax.ws.rs.core.MultivaluedMap;
import org.glassfish.jersey.media.multipart.FormDataParam;
@POST
public Response doPost(MultivaluedMap<String, String> formParams, @FormParam("property") String xpath,
@FormDataParam("content") Blob blob) {
DocumentModel doc = getTarget().getAdapter(DocumentModel.class);
DocumentHelper.fillDocument(doc, formParams);
if (xpath == null || blob == null) {
throw new RuntimeException("Illegal parameters");
}
...
}
The @FormParam
annotations allow retrieving the value of the named form parameter. If you need dynamic access
to form parameters, you should declare one parameter with MultivaluedMap<String, String>
that will collect them.
ModuleRoot Does Not Hold HttpServletRequest Anymore
The HttpServletRequest request
field present in ModuleRoot
has been removed as it is not needed anymore since
WebContext
can be retrieved from the Jakarta Context.
If you need it, just declare it as a field or method parameter in your code:
@Context
protected HttpServletRequest request;
WebEngine Module Singletons
The support of singletons has been marked as deprecated in Jakarta RS 3, we encourage you to update your WebEngine Module
getSingletons
method to getClasses
and leverage the new Jersey Dependency Injection engine to handle their lifecycle.
If your WebEngine Module looks like this:
public class MyModule extends WebEngineModule {
@Override
public Set<Object> getSingletons() {
var result = new HashSet<>();
result.add(new MyModuleExceptionMapper());
return result;
}
}
public class MyModuleExceptionMapper implements ExceptionMapper<Exception> {
@Override
public Response toResponse(Exception exception) {
return Response.status(500).build();
}
}
You should update it to:
public class MyModule extends WebEngineModule {
@Override
public Set<Class<?>> getClasses() {
var result = new HashSet<Class<?>>();
result.add(MyModuleExceptionMapper.class);
return result;
}
}
import jakarta.inject.Singleton;
import jakarta.ws.rs.ext.Provider;
@Singleton
@Provider
public class MyModuleExceptionMapper implements ExceptionMapper<Exception> {
@Override
public Response toResponse(Exception exception) {
return Response.status(500).build();
}
}