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

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

Open
maloewe-ona opened this issue Mar 31, 2024 · 2 comments

Comments

@maloewe-ona
Copy link

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.

@antoniosanct
Copy link
Contributor

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

Regards,
Antonio.

@maloewe-ona
Copy link
Author

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.

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

2 participants