Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ClassLoader issues when used with Spring Boot on Java 19(+) #699

Open
zbocsor opened this issue Nov 18, 2024 · 0 comments
Open

ClassLoader issues when used with Spring Boot on Java 19(+) #699

zbocsor opened this issue Nov 18, 2024 · 0 comments

Comments

@zbocsor
Copy link

zbocsor commented Nov 18, 2024

Creating a Thread using its 5 parameter constructor with the inheritInheritableThreadLocals parameter set to false will result in using the system class loader since Java 19 (see this line in the JDK source) This means that the ThreadHelper.JDK9ThreadFactory will create threads that before Java 19 would inherit the parent thread's context class loader, however after Java 19, the created threads will use the system class loader.

This does not play nice with Spring Boot generated executable jars (see https://docs.spring.io/spring-boot/specification/executable-jar/index.html). This can cause some code running on "jaxws-engine-" threads to behave differently with Java 19(+).

Specifically on a project I'm currently working on, I noticed strange errors while trying to upgrade to Java 21. See this stack trace:

java.util.concurrent.ExecutionException: jakarta.xml.ws.WebServiceException: java.lang.Error: jakarta.xml.bind.JAXBException: Implementation of Jakarta XML Binding-API has not been found on module path or classpath.
 - with linked exception:
[java.lang.ClassNotFoundException: org.glassfish.jaxb.runtime.v2.ContextFactory]
	at com.sun.xml.ws.util.CompletedFuture.get(CompletedFuture.java:50)
	at [*********]
	at com.sun.xml.ws.client.AsyncResponseImpl.set(AsyncResponseImpl.java:104)
	at com.sun.xml.ws.client.sei.AsyncMethodHandler$SEIAsyncInvoker$1.onCompletion(AsyncMethodHandler.java:179)
	at com.sun.xml.ws.client.Stub$1.onCompletion(Stub.java:528)
	at com.sun.xml.ws.api.pipe.Fiber.completionCheck(Fiber.java:897)
	at com.sun.xml.ws.api.pipe.Fiber.run(Fiber.java:792)
	at com.sun.xml.ws.api.server.ThreadLocalContainerResolver$2$1.run(ThreadLocalContainerResolver.java:89)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
	at java.base/java.lang.Thread.run(Thread.java:1570)
Caused by: jakarta.xml.ws.WebServiceException: java.lang.Error: jakarta.xml.bind.JAXBException: Implementation of Jakarta XML Binding-API has not been found on module path or classpath.
 - with linked exception:
[java.lang.ClassNotFoundException: org.glassfish.jaxb.runtime.v2.ContextFactory]
	... 8 common frames omitted
Caused by: java.lang.Error: jakarta.xml.bind.JAXBException: Implementation of Jakarta XML Binding-API has not been found on module path or classpath.
 - with linked exception:
[java.lang.ClassNotFoundException: org.glassfish.jaxb.runtime.v2.ContextFactory]
	at com.sun.xml.ws.fault.ExceptionBean.<clinit>(ExceptionBean.java:173)
	at com.sun.xml.ws.fault.SOAPFaultBuilder.attachServerException(SOAPFaultBuilder.java:275)
	at com.sun.xml.ws.fault.SOAPFaultBuilder.createException(SOAPFaultBuilder.java:124)
	at com.sun.xml.ws.client.sei.StubHandler.readResponse(StubHandler.java:225)
	at com.sun.xml.ws.db.DatabindingImpl.deserializeResponse(DatabindingImpl.java:176)
	at com.sun.xml.ws.db.DatabindingImpl.deserializeResponse(DatabindingImpl.java:263)
	at com.sun.xml.ws.client.sei.AsyncMethodHandler$SEIAsyncInvoker$1.onCompletion(AsyncMethodHandler.java:148)
	... 7 common frames omitted
Caused by: jakarta.xml.bind.JAXBException: Implementation of Jakarta XML Binding-API has not been found on module path or classpath.
	at jakarta.xml.bind.ContextFinder.newInstance(ContextFinder.java:250)
	at jakarta.xml.bind.ContextFinder.newInstance(ContextFinder.java:238)
	at jakarta.xml.bind.ContextFinder.find(ContextFinder.java:386)
	at jakarta.xml.bind.JAXBContext.newInstance(JAXBContext.java:605)
	at jakarta.xml.bind.JAXBContext.newInstance(JAXBContext.java:546)
	at com.sun.xml.ws.fault.ExceptionBean.<clinit>(ExceptionBean.java:170)
	... 13 common frames omitted
Caused by: java.lang.ClassNotFoundException: org.glassfish.jaxb.runtime.v2.ContextFactory
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
	at jakarta.xml.bind.ServiceLoaderUtil.nullSafeLoadClass(ServiceLoaderUtil.java:113)
	at jakarta.xml.bind.ServiceLoaderUtil.safeLoadClass(ServiceLoaderUtil.java:146)
	at jakarta.xml.bind.ContextFinder.newInstance(ContextFinder.java:248)
	... 18 common frames omitted

This seems to happen because the completion handler runs on the "jaxws-engine-" threads but outside of a Fiber's _doRun method. This causes the code to use the original context class loader of that thread, which in Java 19(+) is the system class loader. However as our project is run from a Spring Boot generated executable jar, this is incorrect, because it should use Spring Boot's class loader instead.

My proposal to fix this is by explicitely setting the context class loader of the threads created by ThreadHelper.JDK9ThreadFactory to the parent thread's class loader.
This seems to be the proper solution according to this JDK ticket: https://bugs.openjdk.org/browse/JDK-8290003?jql=labels%20%3D%20JEP-425.
Spring Boot documentation mentions this as well: https://docs.spring.io/spring-boot/specification/executable-jar/restrictions.html.

I created the following PR for this: #698.
@lukasj Kindly review and share your feedback. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant