Move from javax to jakarta (Part 2): customize your dependencies
In previous post, we customized a TomEE 9 instance to be even more jakarta. In this post we will check how to handle our javax dependencies.
At that point, we have a TomEE "super jakarta ready" but it does not mean all applications will run fine. The most common pitfall is the (unexpected/undetected) usage of a javax library.
Let's take a simple example: you have a JSF application and use one of the mainstream JSF libraries (Primefaces - jakarta version is coming but not yet available, bootfaces, omnifaces in javax version - a jakarta version exists). In this case, you likely depend on two kind of API these libraries provide:
- Some custom classes (com.library package, no javax/jakarta),
- Some templates/xhtml/jsp so still no explicit jakarta import.
Note: it is the case even of simpler libraries like bean validation or so, you can use a @CustomValidation annotation from com.library package and you don't always know if it uses javax or jakarta and a search does not find any javax usage.
So this means you can depend on javax without knowing it by a simple search (a complex one would go through all your stack code but it is a bit more complex to do right).
In practise, you often have a doubt and when you start you starts to see weird errors:
Skipping deployment of Class class com.github.rmannibucau.MyJsfController due to a NoClassDefFoundError:Lorg/library/JsfApi
It can even be more brutal ClassNotFoundException (previous one is a warning). But at the end, the application does not work as expected even if well coded with jakarta imports and deployed in a TomEE 9 fully jakarta friendly.
The issue is that the library the application uses imports some javax classes and since they are not there the class can't be loaded and since the class can't be loaded your own class can't as well or fails at runtime depending the case.
Once understood, the solution is trivial, no? Just update. Well, in practise it is not always that trivial:
- The library is not yet released in jakarta flavor
- The library exists in jakarta flavor but broke some API or didn't port some API so the application can't migrate like that
- Your project has some version policy preventing you to migrate.
So we are done, we are sticked to javax....not really. Several open source project created javax to jakarta migration tools. I'll not detail them all but the one hosted at Tomcat is quite interesting and easy to use. Last releases is the 0.1.0 and works generally well but I would recommend you to either use the 0.1.1-SNAPSHOT or wait for the next release (or ask for it on tomcat list ;)) since it brings some fixes which impact Primefaces.
All these tools rewrite the code using javax to use jakarta. The main difference is about the implementation and setup. The common setups are:
- A CLI (Tomcat migration tool use a CLI oriented solution): either a standalone program or a plugin for Ant/Maven/Gradle.
- A javaagent: adding a javaagent on your JVM the migration can be done on the fly, it slows down the startup but works well.
- A ClassLoader ClassFileTransformer: very close to the javaagent but enables to set it only on the webapp classloader it can enable to host multiple applications with different configuration and get overall a faster startup with a finer configuration. It can also be set up automatically at container level instead of having to be done on the JVM.
So in our case we will use Tomcat migration tool. It is provided either as a CLI or Ant task. The Ant task can be executed with Maven/Gradle but there is actually no real point since the CLI too and is easy to setup with exec-maven-plugin:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution> <!-- 1 -->
<id>migrate-primefaces</id>
<phase>none</phase> <!-- 2 -->
<goals>
<goal>java</goal>
</goals>
<configuration>
<!-- 3 -->
<mainClass>org.apache.tomcat.jakartaee.MigrationCLI</mainClass>
<arguments>
<argument>-profile=EE</argument> <!-- 4 -->
<!-- 5 -->
<argument>${project.build.directory}/tomee/standalone/webapps/ROOT/WEB-INF/lib/primefaces-7.0.jar</argument>
<argument>${project.build.directory}/tomee/standalone/webapps/ROOT/WEB-INF/lib/primefaces-7.0.jar</argument>
</arguments>
</configuration>
</execution>
</executions>
<configuration> <!-- 6 -->
<includePluginDependencies>true</includePluginDependencies>
<includeProjectDependencies>false</includeProjectDependencies>
</configuration>
<dependencies>
<dependency> <!-- 7 -->
<groupId>org.apache.tomcat</groupId>
<artifactId>jakartaee-migration</artifactId>
<version>0.1.1-SNAPSHOT</version>
</dependency>
</dependencies>
</plugin>
- We define one execution per library to migrate,
- We can bind this automatically or not to a phase, here we will execute it on the library in TomEE standalone instance so we will do it explicitly from the command line, so no automatic execution,
- We set the main class of the migration tool,
- We set the migration profile. For now Tomcat tool has two, one for tomcat stack only and one for EE which is the one we need there,
- We set the library source and target path. Since we want to rewrite the library the TomEE plugin copied from the war plugin build, we set the same location and tomcat migration tool will rewrite the file in place,
- In terms of dependencies we don't want our execution use project classpath but we need to add the tomcat migration tool in the classpath so we will do it though plugin dependencies,
- Because of 6 we add tomcat migration tool there.
TIP: you can do the same kind of rewriting using maven-shade-plugin, using its relocation, it just requires some more tuning and another module to do the relocation properly. This option is very interesting if you build a stack and not just a final application.
Now if we execute:
mvn exec:java@migrate-primefaces
You will see this output:
[INFO] --- exec-maven-plugin:3.0.0:java (migrate-primefaces) @ app ---
Performing migration from source [/project/target/tomee/standalone/webapps/ROOT/WEB-INF/lib/primefaces-7.0.jar] to destination [/opt/rmannibucau/dev/KMI/target/tomee/standalone/webapps/ROOT/WEB-INF/lib/primefaces-7.0.jar] with Jakarta EE specification profile [EE]
Migrating archive [primefaces-7.0.jar]
Migration completed successfully [true] in [1,002] milliseconds
Which means the jar was migrated to jakarta. If you go in target/tomee/standalone (don't forget in previous post we tuned the default tomee distribution folder), you can run ./bin/catalina.sh run which will start your primefaces application which should now work as expected.
From the same author:
In the same category: