I have a module that used javax.mail
and javax.activation
. Now I moved to Jakarta. I updated the dependencies and all the imports.
When I run it locally it runs fine, but when running it in Docker container I have the following error:
java.lang.NoClassDefFoundError: javax/activation/DataContentHandler
at java.base/java.lang.ClassLoader.defineClass1(Native Method)
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1027)
at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150)
at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:862)
at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:760)
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:681)
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:639)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
at java.base/java.lang.ClassLoader.defineClass1(Native Method)
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1027)
at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:150)
at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:862)
at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:760)
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:681)
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:639)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
at jakarta.activation.MailcapCommandMap.getDataContentHandler(MailcapCommandMap.java:594)
at jakarta.activation.MailcapCommandMap.createDataContentHandler(MailcapCommandMap.java:572)
at jakarta.activation.CommandMap.createDataContentHandler(CommandMap.java:205)
at jakarta.activation.DataHandler.getDataContentHandler(DataHandler.java:587)
at jakarta.activation.DataHandler.getContent(DataHandler.java:514)
at jakarta.mail.internet.MimeMessage.getContent(MimeMessage.java:1504)
at org.assimbly.mail.component.mail.MailBinding.extractBodyFromMail(MailBinding.java:302)
at org.assimbly.mail.component.mail.MailMessage.createBody(MailMessage.java:93)
at org.apache.camel.support.MessageSupport.getBody(MessageSupport.java:68)
at org.assimbly.mail.component.mail.MailConsumer.createExchange(MailConsumer.java:406)
at org.assimbly.mail.component.mail.MailConsumer.retrieveMessages(MailConsumer.java:328)
at org.assimbly.mail.component.mail.MailConsumer.poll(MailConsumer.java:164)
at org.apache.camel.support.ScheduledPollConsumer.doRun(ScheduledPollConsumer.java:205)
at org.apache.camel.support.ScheduledPollConsumer.run(ScheduledPollConsumer.java:119)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:358)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: java.lang.ClassNotFoundException: javax.activation.DataContentHandler
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
... 38 common frames omitted
In the error there is a reference to Jakarta classes that are called, but at the end it refers to javax.activation not found. I checked the dependency tree, and there are no references outside of Jakarta. I also try to force to use Jakarta in Gradle, but with the same result.
How can I find out what causing it to still call javax?
The Java Mail project has a long history. It started already in 1998 and later became part of Java EE (Enterprise Edition). It also uses the JavaBeans Activation Framework (JAF). This framework helps Java programs figure out how to handle different types of data (like files or emails). It allows the program to:
Detect the type of data (e.g., image, text, PDF).
Pick the right tool (or handler) to work with that data (e.g., open, view, or convert it).
The activation framework was moved out of the JDK since JDK9. In 2019 all Java EE libraries moved to Jakarta EE, and in 2021 Jakarta mail became part of the Angus project at the Eclipse foundation. If you want to use the latest version on your project than use Angus.
When upgrading to Angus/Jakarta mail there are 3 areas to look into:
To use the right dependencies use both the jakarta.activation-api/jakarta.mail-api and the angus-activation/angus-mail (especially when you are calling DataHandlers directly in your code). The dependencies should look like this:
<dependency>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>org.eclipse.angus</groupId>
<artifactId>angus-activation</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>jakarta.mail</groupId>
<artifactId>jakarta.mail-api</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>org.eclipse.angus</groupId>
<artifactId>angus-mail</artifactId>
<version>2.0.3</version>
</dependency>
After upgrading use the right imports, as probably recommended by your IDE. Also check if there is a providers file in the META-INF directory. There the reference should also point to the new library. After upgrading the classes, it could look something like this:
# Jakarta Mail IMAP provider Oracle
protocol=imap; type=store; class=org.eclipse.angus.mail.imap.IMAPStore; vendor=Oracle;
protocol=imaps; type=store; class=org.eclipse.angus.mail.imap.IMAPSSLStore; vendor=Oracle;
# Jakarta Mail SMTP provider Oracle
protocol=smtp; type=transport; class=org.eclipse.angus.mail.smtp.SMTPTransport; vendor=Oracle;
protocol=smtps; type=transport; class=org.eclipse.angus.mail.smtp.SMTPSSLTransport; vendor=Oracle;
# Jakarta Mail POP3 provider Oracle
protocol=pop3; type=store; class=org.eclipse.angus.mail.pop3.POP3Store; vendor=Oracle;
protocol=pop3s; type=store; class=org.eclipse.angus.mail.pop3.POP3SSLStore; vendor=Oracle;
After upgrading you mentioned that it works locally, but not on your Docker container (created by Jib). Probably this is because the application is exploded on the image. Alternatively you could set the containerizedMode=packaged which probably avoids another way of loading (check the jib documentation of how to change the entrypoint if you use a custom entrypoint).
When exploded, then the libraries maybe loaded in different order (especially when using wildcards). The legacy code and current code in activation/mail that works like ServiceLoader doesn't check the resolved class name in the text files are assignable in current classloader. This may cause the error.
The important thing in the StackTrace is:
java.lang.NoClassDefFoundError: javax/activation/DataContentHandler
This means that there must be an older library on the classpath that is referencing javax.activation.DataContentHandler. If you isolate your new Jakarta upgraded part this should not happen.
Unfortunately the error doesn't give information which library is causing the conflict. You need to search it with maven (mvn dependency tree) or gradle (gradle dependencies). Start first at the main module/project. If there you can not find it, move to the next module/project. Look not only to "activation" dependencies as you can read in the error, but also look for others like "mailapi", "com.sun.mail" etc. If you find one try to exclude it from the project or change the scope of the dependency.