How to handle the Spring Boot classloader 'problem'

99 Views Asked by At

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:

  1. What is the condition for the worker threads to run into this situation?
    There are 'only some' situations where the worker threads with the AppClassLoader are running into a ClassNotFoundException. 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
  2. When a worker thread fails to execute JAXBContext.newInstance(ObjectFactory.class) with ClassNotFoundException: org.glassfish.jaxb.runtime.v2.ContextFactory - why is this thread able to successfully execute Class.forName("org.glassfish.jaxb.runtime.v2.ContextFactory")
  3. 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 using Streams.parallel() or more general using the 'standard' ForkJoinPool without 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.

0

There are 0 best solutions below