Upgrade to Jakarta EE 10 – part 3: Transform incompatible Dependencies

This entry is part 4 of 4 in the series Upgrading to Jakarta EE 10

In this article, we’ll address upgrading individual libraries used by your applications. This solves two problems. First, it improves the build time of your application during development and reduces the build time introduced by transforming the final binary after each build. And second, it solves compilation problems you can face with some libraries after you adjust the source code of your application for Jakarta EE 10.

Earlier, we described how to transform an application binary, e.g. a WAR file, to make it compatible with Jakarta EE 10 so that it can be deployed to GlassFish 7. But this transformation is slow and needs to be done with every build. This doesn’t make developers happy because it increases the time to build and deploy the application after they made changes in the source code.

We also described how to automate transforming the application’s source code to compile it with the Jakarta EE 10 APIs. However, after doing this, there’s a high chance your application won’t compile. This is because some of the libraries used by your application may not be compatible with Jakarta EE 10. They are transformed after the application is built but that’s too late.

In this article, we’ll explain a few simple approaches how to make sure that external libraries are compatible with Jakarta EE 10 so that everything correctly compiles and no transformation is necessary after each build.

What’s the problem, really?

Libraries in your application fall into these categories:

  • The library doesn’t use Java EE APIs at all – no problem here, just continue using it as before
  • There’s a version of the library compatible with Jakarta EE 10 and you can update to this version – just update it.
  • The library doesn’t have a version compatible with Jakarta EE 10 or you can’t update it for some reason. It needs to be transformed.
  • The library depends on features removed in Jakarta EE 10 and cannot be updated to a version compatible with Jakarta EE 10. Tough luck, no simple solution here, though this category is very rare.

While, obviously, the first category doesn’t cause any problems, libraries that use APIs not compatible with Jakarta EE 10 need some treatment. Libraries that have a version compatible with Jakarta EE 10 can be simply updated to this version. We’ll describe some examples of such widely used libraries below.

Some other libraries have not yet been updated for Jakarta EE 10. Or you cannot afford to update them to a new version for whatever reason (risk of regression, missing feature in the new version, etc.). Then you’ll need to transform them yourself into a version compatible with Jakarta EE 10. This can be done outside of your application project so that you transform the libraries once and then use the transformed library when building the application.

In some rare cases, you may come across a library, which is not compatible with Jakarta EE 10 even after the transformation, because it depends on some old APIs removed in Jakarta EE 10. Each such library may require specific treatment, and describing the techniques which can be used would be for another whole article. Therefore we’ll not address these rare cases now.

Update libraries to a Jakarta EE 10 version

Most of the libraries widely used in enterprise projects already support Jakarta EE 10 so it’s easy to just update them to a newer version.

For example, to update the Hibernate library, just increase the version number. Here’s an example for the version 6.2.7.Final, the latest version at this moment:

<dependency>
  <groupId>org.hibernate.orm</groupId>
  <artifactId>hibernate-core</artifactId>
  <version>6.2.7.Final</version>
</dependency>

However, some libraries maintain support for both Jakarta EE 9+ and older Jakarta EE and Java EE versions. Those libraries have two variants for the same library version. In that case, their Maven artifact for Jakarta EE 9+ is usually published with the same coordinates as before but with the jakarta classifier. You need to specify an additional <classifier>jakarta</classifier> configuratoin in the dependency definition, so that the correct variant is downloaded and used in your application. For example, to update the Primefaces library to version 13 and Jakarta EE 10 variant (jakarta classifier):

<dependency>
  <groupId>org.primefaces</groupId>
  <artifactId>primefaces</artifactId>
  <version>13.0.0</version>
  <classifier>jakarta</classifier>
</dependency>

Some other libraries also provide both variants but the Maven artifact is published under different coordinates. One example is the Jackson library, which publishes the artifacts in a completely different groupId and artifactId which contain jakarta in the name:

<dependency>
  <groupId>com.fasterxml.jackson.jakarta.rs</groupId>
  <artifactId>jackson-jakarta-rs-json-provider</artifactId>
  <version>2.15.2</version>
</dependency>

Popular libraries which support Jakarta EE 9+

Here are some examples of popular libraries that support Jakarta EE 9+ in their recent versions (either the main artifact or variant with the jakarta classifier):

Library nameMaven dependency definition
Hibernateorg.hibernate.orm:hibernate-core
Omnifacesorg.omnifaces:omnifaces
Jacksoncom.fasterxml.jackson.jakarta.rs:jackson-jakarta-rs-json-provider
Apache Deltaspikeorg.apache.deltaspike.modules (all artifacts with the jakarta classifier)
Primefacesorg.primefaces:primefaces (jakarta classifier)
Spring Framework 6https://spring.io/
Spring Boot 3https://spring.io/projects/spring-boot

Transform libraries for Jakarta EE 10

When it’s not possible to upgrade a library, we can transform individual libraries with the Eclipse Trasformer using a similar technique to transforming the whole application WAR which we explained in a previous article. You can use the Eclipse Transformer also on individual library JARs and then use the transformed JARs during the build. However, in modern Maven or Gradle based projects, this isn’t natural because of transitive dependencies. There’s currently no tooling that would properly transform all the transitive dependencies and install them correctly to a local repository. Therefore we’ll use a trick – we’ll merge all JARs that need to be transformed into a single JAR (Uber JAR), with all the transitive dependencies, then transform it, and then install this single JAR into a Maven repository. Then we’ll change the application to depend on this single artifact instead of depending on all the individual artifacts.

First, here’s an example list of dependencies from pom.xml file of a Maven project which aren’t compatible with Jakarta EE 10:

File pom.xml in the “application” project:

<groupId>ee.omnifish</groupId>
<artifactId>jakarta-app</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>

<dependencies>
 
  <!-- Jakarta EE 8 API -->
  <dependency>
    <groupId>jakarta.platform</groupId>
    <artifactId>jakarta.jakartaee-api</artifactId>
    <version>8.0.0</version>
    <scope>provided</scope>
  </dependency>

  <!-- incompatible with Jakarta EE 10 API -->
  <dependency>
    <groupId>net.sf.jasperreports</groupId>
    <artifactId>jasperreports</artifactId>
    <version>6.20.1</version>
  </dependency>
  <dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.2</version>
  </dependency>

</dependencies>

With this as a starting point, we’ll create a new Maven project next to our existing project. For convenience, we can move both projects into a Maven POM project as modules so that we can build everything together if needed. We will move all the dependencies we need to transform into this new project. We will remove all those dependencies from the original project and replace them with a single dependency on the new project.

The pom.xml of new the project would look like this:

Snippet of the pom.xml file in a new “transform-dependencies” project:

<groupId>ee.omnifish.transformed</groupId>
<artifactId>transform-dependencies</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<dependencies>
    <!-- dependencies not compatible with Jakarta EE 10+
    - will be transformed in this JAR artifact -->
    <dependency>
        <groupId>net.sf.jasperreports</groupId>
        <artifactId>jasperreports</artifactId>
        <version>6.20.1</version>
    </dependency>
    <dependency>
        <groupId>org.quartz-scheduler</groupId>
        <artifactId>quartz</artifactId>
        <version>2.3.2</version>
    </dependency>
</dependencies>

In the final WAR file, instead of having each JAR file separately in the WAR, like this:

  • WEB-INF
    • classes
      • jasperreports.jar
      • quartz.jar

We will end up with a single transformed JAR, like this:

  • WEB-INF
    • classes
      • transform-dependencies.jar

This transform-dependencies.jar file will contain all the artifacts merged into it – it will contain all classes and files from all the artifacts.

In order to achieve this, we can use the Maven Shade plugin, which merges multiple JAR files into a single artifact produced by the project:

Snippet of the pom.xml file in a new “transform-dependencies” project:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-shade-plugin</artifactId>
  <version>3.5.0</version>
  <executions>
    <execution>
      <phase>package</phase>
      <goals>
        <goal>shade</goal>
      </goals>
      <configuration>
        <shadedClassifierName>jakarta</shadedClassifierName>
        <shadedArtifactAttached>true</shadedArtifactAttached>
        <transformers>
          <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
          <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"/>
        </transformers>
      </configuration>
    </execution>
  </executions>
</plugin>

This plugin takes all the dependencies defined in the project, merges them into a single Uber JAR and attaches the JAR to the project as an artifact with the jakarta classifier. It would be nicer if it attached the JAR as the main artifact, without the classifier. This would cause a conflict with the Transformer plugin we need to use on the Uber JAR. Therefore we use an extra classifier jakarta here and we’ll need to use this classifier also in the original project when we define the dependency on this new project.

Now we add the Transformer plugin to transform the Uber JAR to make it compatible with Jakarta EE 9+. We need to configure the Transformer plugin with the following:

  • execute the goal “jar”
  • use the “jakartaDefaults” rule to apply transformations for Jakarta EE 9
  • define artifact with the classfier “jakarta” produced by the shade maven plugin. This will have the same groupId, artifactId and version as the current project

Snippet of the pom.xml file in a new “transform-dependencies” project:

<plugin>
  <groupId>org.eclipse.transformer</groupId>
  <artifactId>transformer-maven-plugin</artifactId>
  <version>0.5.0</version>
  <executions>
    <execution>
    <id>jar</id>
    <phase>package</phase>
    <goals>
      <goal>jar</goal>
    </goals>
    </execution>
  </executions>
  <configuration>
    <rules>
      <jakartaDefaults>true</jakartaDefaults>
    </rules>
    <artifact>
      <groupId>${project.groupId}</groupId>
      <artifactId>${project.artifactId}</artifactId>
      <classifier>jakarta</classifier>
    </artifact>
  </configuration>
</plugin>

We can now build the transform-dependencies project with the standard Maven command:

mvn install

The original pom.xml file should now only depend on this new artifact:

<groupId>ee.omnifish</groupId>
<artifactId>jakarta-app</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>

<dependencies>
 
  <!-- Jakarta EE 8 API -->
  <dependency>
    <groupId>jakarta.platform</groupId>
    <artifactId>jakarta.jakartaee-api</artifactId>
    <version>8.0.0</version>
    <scope>provided</scope>
  </dependency>

  <!-- Uber JAR artifact that includes classes from all 
        the dependencies that need to be transformed -->
  <dependency>
    <groupId>ee.omnifish.transformed</groupId>
    <artifactId>transform-dependencies</artifactId>
    <version>1.0-SNAPSHOT</version>
    <classifier>jakarta</classifier>
  </dependency>

</dependencies>

When we now build the original application project, it will use the transformed Uber JAR in the build and add it to the final WAR instead of all individual (untransformed) JARs. We can now deploy this WAR to a Jakarta EE 10 application server like GlassFish 7. If none of the original JARs depends on APIs removed between Jakarta EE 9 and 10, the application should now work as expected!

A full example of this approach is here: https://github.com/OmniFish-EE/upgrading-jakarta-ee-applications/tree/main/javax-jakarta-transform-dependencies-uberjar

Evolve the project in the future

In the future, it’s likely that some of the libraries we need to transform with the transform-dependencies project will become compatible with Jakarta EE 10. We can then easily update them and stop transforming them. We just need to move add the dependency back to the original “application” pom.xml file with a new version number, and remove it from the dependencies in the transform-dependencies project. The application will thus start depending an official version of the library without transformation. Eventually, if you’re able to update all of the libraries, you can discard the transform-dependencies project and keep a single native Jakarta EE 10 project. This approach with a helper transform-dependencies project is only a temporary solution until the time you can update all the libraries to Jakarta EE 10.

Conclusion

In this article, we described the last piece of the migration process which can be fully automated with very little effort. If you combine all the steps described here and in the previous articles in this series, you’ll be able to migrate your older Java EE project to Jakarta EE 9. In case your project doesn’t depend on any APIs dropped in Jakarta EE 10, your migration will be completed and you can start using new features in Jakarta EE 10. If you’re less fortunate, there’s still some work to do to refactor your code or adjust libraries that use the removed APIs to start using newer alternative APIs in Jakarta EE 10 that replace them. Most of these refactorings can be automated with custom Eclipse Transformer rules but some are more complicated and hard to automate. We’ll deal with them in future articles.

Resources

A Github repository with sample applications: https://github.com/OmniFish-EE/upgrading-jakarta-ee-applications/#readme

Series Navigation<< Upgrade to Jakarta EE 10 – part 2: Transform Application Source Code

Leave a Comment

Your email address will not be published. Required fields are marked *

Captcha loading...