5

I use jaxb for unmarshalling, but when I use a ForkJoinPool execute() method, I get a "java.log.ClassNotFoundException: com.sun.xml.internal.bind.v2.ContextFactory", but I'm sure of the presence in the classpath in my runtime, because when I don't use a ForkJoinPool it work properly ... do you know a problem or workaround for that ?

I use Java 11

my code:

ForkJoinPool commonPool = ForkJoinPool.commonPool();
        commonPool.execute(() -> {
            try {
                String messageFileContent = Files.readString(Paths.get(
                        this.getClass().getClassLoader().getResource("xml-to-process.xml").getPath()));

                JAXBContext jaxbContext = JAXBContext.newInstance(ObjectFactory.class);
                Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
                // avoiding schema validation for more performance
                jaxbUnmarshaller.setSchema(null);
                UpdateWorkOrder updateWorkOrder = (UpdateWorkOrder) jaxbUnmarshaller.unmarshal(new StringReader(messageFileContent));
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

it's very strange no ... ? Outside the execute() of ForkJoinPool, the parsing was correclty performed.

The error is:

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]
java.base/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(Unknown Source)
at java.base/java.util.concurrent.ForkJoinTask.doExec(Unknown Source)
at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(Unknown Source)
at java.base/java.util.concurrent.ForkJoinPool.scan(Unknown Source)
at java.base/java.util.concurrent.ForkJoinPool.runWorker(Unknown Source)
at java.base/java.util.concurrent.ForkJoinWorkerThread.run(Unknown Source)
Caused by: java.lang.ClassNotFoundException: com.sun.xml.internal.bind.v2.ContextFactory
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(Unknown Source)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(Unknown Source)
at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
at javax.xml.bind.ServiceLoaderUtil.nullSafeLoadClass(ServiceLoaderUtil.java:92)
at javax.xml.bind.ServiceLoaderUtil.safeLoadClass(ServiceLoaderUtil.java:125)
at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:230)
Adrien Ruffie
  • 195
  • 4
  • 16

3 Answers3

5

While NightShade's answer completely describes the problem, let me add some details.

The Servlet API world and the concurrent world are quite incompatible: the former assumes that all computation is done on the same thread (ServletRequest#startAsync is a relatively recent addition). Therefore applications and libraries using the Servlet API often attach objects to the current Thread, e.g.:

  • the thread executing the request has the application's classloader attached as "context classloader",
  • Spring attaches a RequestContextHolder to the current thread (as ThreadLocal),
  • Vaadin attaches many objects through CurrentInstance,
  • ...

This means that whenever you want to execute something on another thread, you need to copy all these objects onto the destination thread. An easy way to do it is to write a wrapper for your Runnable's (and Callable<T>s), which sets up the environment before starting the execution:

public class WrappedRunnable implements Runnable {

   private final ClassLoader ccl;
   ... // other objects
   private final Runnable    runnable;

   public static Runnable wrap(final Runnable runnable) {
      if (runnable instanceof WrappedRunnable) {
         return runnable;
      }
      return new WrappedRunnable(runnable);
   }

   public WrappedRunnable(final Runnable runnable) {
      this.ccl = Thread.currentThread().getContextClassLoader();
      ... // other objects
      this.runnable = runnable;
   }

   @Override
   public void run() {
      final ClassLoader oldCcl = Thread.currentThread().getContextClassLoader();
      ... // save the value of other objects
      try {
         Thread.currentThread().setContextClassLoader(ccl);
         ... // set the value of other objects
         runnable.run();
      } finally {
         Thread.currentThread().setContextClassLoader(oldCcl);
         // restore the value of other objects
      }
   }
}

You can also write your own ExecutorService (you are actually using the ForkJoinPool as ExecutorService) or ForkJoinPool, which will wrap the Runnables automatically.

Piotr P. Karwasz
  • 12,857
  • 3
  • 20
  • 43
4

This can occur if the ForkJoinPool is using a different ClassLoader than your application, especially if you are using a web container like Tomcat.

You can try setting a thread factory for your ForkJoinPool, and within that factory, set the context classloader of the created thread to the correct application classloader.

NightShade
  • 141
  • 1
  • 8
  • Thank NightShare, I see the bug in Oracle bug database, I use SpringBoot (so embedded Tomcat I think for the swagger), but when I can set the context factory ? Because I try to use this https://stackoverflow.com/questions/49113207/completablefuture-forkjoinpool-set-class-loader but not working properly – Adrien Ruffie Feb 27 '21 at 06:40
0
Use below method to override Class loader from main thread
final ForkJoinWorkerThreadFactory factory = new ForkJoinWorkerThreadFactory()
{
    @Override           
    public ForkJoinWorkerThread newThread(ForkJoinPool pool)
    {
        final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
        worker.setName("my-thread-prefix-name-" + worker.getPoolIndex());
     // worker.setLoader(copy current thread class loader);
        return worker;
    }
};

forkJoinPool = new ForkJoinPool(Runtime.getRuntime().availableProcessors(), factory, null, false); 
chetan mahajan
  • 723
  • 7
  • 9