jaxb-api icon indicating copy to clipboard operation
jaxb-api copied to clipboard

`ContextFinder` cannot find implementation when thread context class loader contains no JAXB jars

Open maloewe-ona opened this issue 10 months ago • 3 comments

Relates to #99 (maybe even the same as that issue, but the issue still occurs with the latest jaxb-api version)

Version

  • jakarta.xml.bind:jakarta.xml.bind-api:4.0.2
  • org.glassfish.jaxb:jaxb-runtime:4.0.5

JDK 21

Description

JAXB's ContextFinder is unable to find the JAXBContext implementation when the thread context class loader contains none of the JAXB jars. JAXBContext.newInstance fails with:

Exception in thread "main" 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 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 org.example.Main.main(Main.java:17)
Caused by: java.lang.ClassNotFoundException: org.glassfish.jaxb.runtime.v2.ContextFactory
	at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:445)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:593)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:526)
	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)
	... 5 more

The problem might be that jakarta.xml.bind.ServiceLoaderUtil uses only ServiceLoader.load(Class), which implicitly uses the thread context class loader. Maybe it should fall back to using ServiceLoader.load(Class, ClassLoader) (and use ServiceLoaderUtil.class.getClassLoader())? But I am not sure if that could have any undesired implications.

Reproduction steps

Create a small Maven project:

  • /pom.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>org.example</groupId>
        <artifactId>temp-jaxb-test</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <properties>
            <maven.compiler.release>21</maven.compiler.release>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>jakarta.xml.bind</groupId>
                <artifactId>jakarta.xml.bind-api</artifactId>
                <version>4.0.2</version>
            </dependency>
            <dependency>
                <groupId>org.glassfish.jaxb</groupId>
                <artifactId>jaxb-runtime</artifactId>
                <version>4.0.5</version>
            </dependency>
        </dependencies>
    </project>
    
  • /src/main/java/org/example/Main.java
    package org.example;
    
    import jakarta.xml.bind.JAXBContext;
    import jakarta.xml.bind.annotation.XmlRootElement;
    import java.net.URL;
    import java.net.URLClassLoader;
    
    public class Main {
        @XmlRootElement
        static class DummyClass {}
    
        public static void main(String[] args) throws Exception {
            ClassLoader parentClassLoader = null;
            Thread.currentThread().setContextClassLoader(new URLClassLoader(new URL[0], parentClassLoader));
    
            var context = JAXBContext.newInstance(DummyClass.class);
    
            var marshaller = context.createMarshaller();
            marshaller.marshal(new DummyClass(), System.out);
        }
    }
    

Then run Main.main; it will fail with the JAXBException mentioned above.

(this is a bit contrived example, but I hope it matches the actual use case described in the "Additional context" section below close enough)

Additional context

I am currently experiencing this while working on a plugin for Jenkins. As described in the Jenkins documentation the context class loader normally does not contain the plugin classes (and therefore not the JAXB classes used by the plugin).

It seems many Jenkins plugins have worked around this by temporarily changing the context class loader, see for example https://issues.jenkins.io/browse/JENKINS-68514. Most of them have done that for the old javax.xml.bind package, but it probably applies to the new jakarta.xml.bind package name as well. At least for the plugin I am working on that seems to be the case.

maloewe-ona avatar Mar 31 '24 14:03 maloewe-ona

@maloewe-ona @lukasj Maybe related with #243 ??

Regards, Antonio.

antoniosanct avatar Apr 18 '24 22:04 antoniosanct

Yes, being able to specify the ClassLoader to use would probably help (unless JAXBContext is used in third-party code you cannot edit). But it would of course be even better if it just worked automatically without having to explicitly specify a ClassLoader, in case that is possible.

maloewe-ona avatar Apr 19 '24 19:04 maloewe-ona