I'm upgrading a Spring Boot 2.2.2 application from Java 8 to 11.
I've faced some ClassNotFoundExceptions because Java 11 doesn't include java ee packages, but after adding the missing jars, the application still cannot find the classes if the thread is created using CompletableFuture
.
The classloaders in these CompletableFuture-threads are different if you execute with jdk8 or jdk11.
So there are ClassNotFoundException
only inside the CompletableFuture thread.
I've checked twice the different migration guides and the issues fixed and cannot found this as a regular behavior so i file it as a bug.
/*
* Java 8 http-nio-8080-exec-1 TomcatEmbeddedWebappClassLoader
* Java 11 http-nio-8080-exec-1 TomcatEmbeddedWebappClassLoader
*/
log.info(Thread.currentThread().getName()+" "+Thread.currentThread().getContextClassLoader().toString());
/*
* Java 8 ForkJoinPool.commonPool-worker-1 TomcatEmbeddedWebappClassLoader
* Java 11 ForkJoinPool.commonPool-worker-3 jdk.internal.loader.ClassLoaders$AppClassLoader@5bc2b487 <- it's different and it cannot load the classes even if the classes are in the final jar
*/
CompletableFuture.runAsync(() -> {
log.info(Thread.currentThread().getName()+" "+Thread.currentThread().getContextClassLoader().toString());
}).join();
Related https://github.com/spring-projects/spring-boot/issues/17796
Comment From: Canos
As a workaround I've tried to configure the async threads overriding methodInvokingFactoryBean
but cannot find a way to find the correct value to set on setBeanClassLoader
@Bean
public MethodInvokingFactoryBean methodInvokingFactoryBean() {
MethodInvokingFactoryBean m = new MethodInvokingFactoryBean();
m.setTargetClass(SecurityContextHolder.class);
m.setTargetMethod("setStrategyName");
m.setArguments(new String[]{SecurityContextHolder.MODE_INHERITABLETHREADLOCAL});
//m.setBeanClassLoader(....);
return m;
}
The problem filed in the issue is happening with and without Spring Security security manager.
Comment From: wilkinsona
The change in behaviour is due to a change in the JDK and is out of Spring Boot’s control. You’ll probably want to use a pool of your own where you can control the TCCL rather than the common pool. If you need some assistance to do that, please ask a question on Stack Overflow. As mentioned in the guidelines for contributing, we prefer to use GitHub issues only for bugs and enhancements.
Comment From: Canos
Thanks Andy, and sorry, i guessed it was a bug
Comment From: elab
Parallel streams use the default ForkJoinPool.commonPool
causing exact the same problem with class loaders in JDK9, JDK10, JDK11, ... Watch out for parallelStream()
, parallel()
in your code.
In my case, it manifested in the error javax.xml.soap.SOAPException: Unable to create SAAJ meta-factory: Provider com.sun.xml.internal.messaging.saaj.soap.SAAJMetaFactoryImpl not found
after switching from Java 8 to Java 11.
The error only occurred when the application was running packaged as JAR (and not in the Eclipse IDE).
UPDATE 2021-01-21: I ended up using ParallelCollectors (perhaps it can be useful to someone):
before:
return list.parallelStream().map(s -> s.myMapFunction()).collect(Collectors.toList());
now:
ExecutorService executor = Executors.newCachedThreadPool();
try {
return list.stream().collect(ParallelCollectors.parallel(s -> s.myMapFunction(), Collectors.toList(), executor, parallelism)).join();
} finally {
executor.shutdown();
}
The Thread Context Class Loader (TCCL) in the parallel threads is TomcatEmbeddedWebappClassLoader
(whose parent is org.springframework.boot.loader.LaunchedURLClassLoader
). Exactly as it was with JDK8. No class loading problems occure anymore.
JAR-packaged stand-alone Spring Boot application (embedded Tomcat), JDK11.
Comment From: ztomic
Parallel streams use the default
ForkJoinPool.commonPool
causing exact the same problem with class loaders in JDK9, JDK10, JDK11, ... Watch out forparallelStream()
,parallel()
in your code.In my case, it manifested in the error
javax.xml.soap.SOAPException: Unable to create SAAJ meta-factory: Provider com.sun.xml.internal.messaging.saaj.soap.SAAJMetaFactoryImpl not found
after switching from Java 8 to Java 11.The error only occurred when the application was running packaged as JAR (and not in the Eclipse IDE).
@elab I've had same issue with JAX-WS and ForkJoinPool and my workaround way using custom ForkJoinThreadFactory and now it is working fine (https://stackoverflow.com/a/57551188/5599629).
And also I had to set javax.xml.soap.MetaFactory
and javax.xml.soap.SAAJMetaFactory
and javax.xml.bind.JAXBContextFactory
system properties because without ForkJoinPool also it was working OK for couple of requests and after that ClassNotFoundException
returned.
(sorry for commenting on closed issue)