In our application we sometimes get the following exception:
javax.xml.bind.JAXBException: Implementation of JAXB-API has not been found on module path or classpath.
- with linked exception:
[java.lang.ClassNotFoundException: com.sun.xml.internal.bind.v2.ContextFactory]
We already found out that this happens only if we use Collection.parallelStream()
but not if we use Collection.stream()
.
We saw that JAXB uses Thread.currentThread().getContextClassLoader()
for loading classes. We also saw, that when using parallelStream()
, the threads for executing our command are using different class loaders. Sometimes it's a org.apache.catalina.loader.WebappClassLoader
, and sometimes it's a jdk.internal.loader.ClassLoaders.AppClassLoader
.
And it now seems, that the AppClassLoader
does not know about the JAXB dependencies, whereas the WebappClassLoader
does.
We are using Java 11 and the following Maven dependencies:
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
Any idea what could be wrong? How can it be, that the AppClassLoader
does not know about our dependencies?
We had a different experience. The issue was that tomcat has a different classloader than each war (I think we're seeing something similar with spring boot apps; void-main was different than that of application-runner)..
Either way, the issue is that the "bootstrap" classloader doesn't have access to the jars in your application - and thus NO jaxb. So if you ever launch a thread (like ForkJoinPoolThread
from StreamXX.parallel()
or ForkJoinPool.commonThreadPool()
) then THOSE threads will be from the bootstrap classloader, NOT your application's classloader.. So if the FIRST time your background task loads JAXB, they will run getClass().getContextClassLoader().getResourceAsStream("xxxx")
and will not find the resource.
Our solution was to launch all background tasks in an explicit thread-pool and have an explicit thread-pool factory.. The thread-pool-factory captures the classloader from the invoking thread (the one initialized by the war or the spring-boot context). This classloader WILL have jaxb and friends.. So now each thread launched from this thread-pool factory has an explicit thr.setContextClassLoader(globalCL);
....
Problem solved (via a hack)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With