“Activation” library missing when sending email from Jetty on JDK11 despite library included via Maven
When trying to send an email, using JavaMail, from my application deployed via the Jetty web server in Docker, I kept on getting the following errors:
java.lang.NoClassDefFoundError: javax/activation/DataSource
Caused by:
java.lang.ClassNotFoundException: javax.activation.DataSource
OK so with the upgrade from Java 8 to Java 11, due to Java 9 Project Jigsaw, a lot of things have been “removed” from the JVM. (Perhaps “removed” is not quite the right word, but they are inaccessible by default.)
The normal solution is to either add a reference to the “activation” module from your module file, or to add the “activation” Maven module to your “pom.xml” dependencies section. (I must admit I always do the latter, to avoid having to deal with Java 9 modules.)
But I had included the activation library that it was complaining was missing. As confirmed by “mvn dependency:tree”:
[INFO] +- com.sun.mail:javax.mail:jar:1.4.4:compile
[INFO] | \- javax.activation:activation:jar:1.1:compile
The issue turned out to be that the “9.4.14-jre11” Jetty Docker image supplies JavaMail itself. But it doesn’t supply the “activation” package, which is required to make it work in Java 9+ :-(
It seems that if a class is both supplied by the application (in the *.war) and by the servlet container (Jetty), the servlet container’s class is used in preference (I had expected the opposite). So my code was using Jetty’s JavaMail, not my own. The classloader used to load Jetty’s JavaMail could not see my application, it could only see Jetty’s classes, and activation wasn’t there, thus the error.
I can see no reason why Jetty includes JavaMail (does it need to send email?). I think either it’s a convenience to applications (but in my opinion applications should include the dependencies that they need) or it’s part of the Servlet spec so Jetty has no choice.
In any case, the solution is easy, simply delete Jetty’s JavaMail. Then there is no competition between my application’s JavaMail and Jetty’s JavaMail. The JavaMail from my application is used, which uses my application’s classloader, thus can see the activation library provided by the application.
The following addition to my Dockerfile solved the problem:
# Jetty supplies Mail but not the activation dependency,
# if you use JavaMail it uses Jetty's JavaMail
# which then fails on Java >= 9 (even if we supply the
# activation dependency in Maven, as Jetty's JavaMail
# uses Jetty's classloader rather than ours)
USER root
RUN rm -r /usr/local/jetty/lib/mail
USER jetty