First of all sorry for the 'problem' in the title, it's not to blame but I didn't find a suitable description.
Secondly, I'm aware of the issues and articles regarding this topic which I'll come to some lines later.
I'm aware of the 'problems' that arise out of the change in the classloader handling with ForkJoin common pools introduced in (I think) Java 9 to fix the bug JDK-8172726.
There are several issues at GitHub/sprin-boot regarding this which are all closed because of 'not a Spring issue but a result of the mentioned JDK fix':
I'm also aware that this only occurs when running a Spring Boot application with java -jar build/libs/my-application-0.0.1-SNAPSHOT.jar and not via gradle bootRun or within my IDE.
When running with java -jar ... the Spring Boot classloader packaged withing the created jar file comes into play and the order of class files and jar files in the class path is changed.
I'm also aware that when running a Spring Boot application from the exploded jar file via java -cp "BOOT-INF/classes:BOOT-INF/lib/*" com.example.MyApplication the problem does not arise because instead of the Spring Boot classloader the AppClassLoader is used.
The problem described in short:
When using a ForkJoin pool (Stream.parallel().forEach(...)) the thread calling this uses the Spring Boot classloader while the worker threads are using the AppClassLoader which 'sometimes' (it's quite not clear to me under which conditions) fails to load classes that the Spring Boot classloader is aware of.
I've written a very small Spring Boot example application that nicely shows that: https://github.com/fnumrich/cnf/tree/main
What my example application does (log statements removed):
IntStream.range(0, 500).parallel().forEach(i -> {
try {
// Fails in worker threads with
// java.lang.ClassNotFoundException: org.glassfish.jaxb.runtime.v2.ContextFactory
JAXBContext.newInstance(ObjectFactory.class);
} catch (Exception e) {
try {
// Succeeds in worker threads
Class.forName("org.glassfish.jaxb.runtime.v2.ContextFactory");
} catch (ClassNotFoundException ex) {
...
}
}
});
If a (worker-)thread fails to create a new JAXBContext because of java.lang.ClassNotFoundException: org.glassfish.jaxb.runtime.v2.ContextFactory the application tries to load exactly this class via Class.forName() which interestingly succeeeds ...
So there are several questions that are not clear to me:
- What is the condition for the worker threads to run into this situation?
There are 'only some' situations where the worker threads with theAppClassLoaderare running into aClassNotFoundException. The one described here (JAXBContext.newInstance(ObjectFactory.class)), another that I've come over and asked here: Java ServiceLoader sometimes does not find registered services - When a worker thread fails to execute
JAXBContext.newInstance(ObjectFactory.class)withClassNotFoundException: org.glassfish.jaxb.runtime.v2.ContextFactory- why is this thread able to successfully executeClass.forName("org.glassfish.jaxb.runtime.v2.ContextFactory") - And the main question: It's quite not clear to me how this should be handled correctly. There are several suggestions out there but all of them result in running a Spring Boot application via
java -jar ...(using the Spring Boot classloader) and usingStreams.parallel()or more general using the 'standard'ForkJoinPoolwithout further preparation is incompatible ...
So at least I am a little confused. Maybe some of the spring dudes is able to bring some light in this situation.