Search code examples
javajaxbclassloader

Jaxb with jdk17 classloader issues


i have an application running in jdk17 that tries to use jaxb, but is failing to load the actual jaxb-runtime.jar, it seems.

I have added jaxb-api.jar 2.3.1 and jaxb-runtime.jar 2.3.4 to the classpath

And i have this code

try (BufferedReader br = new BufferedReader(new java.io.InputStreamReader(getClass().getResourceAsStream("/META-INF/services/javax.xml.bind.JAXBContext")))) {
    String cn = br.readLine();
    Class c = Class.forName(cn);
    System.out.println("Impl class: " + c.getName());
} catch (Exception e) {
    e.printStackTrace();
}

 context = JAXBContext.newInstance(getClass());

When run, i get

Impl class: com.sun.xml.bind.v2.ContextFactory

Caused by: java.lang.ClassNotFoundException: com.sun.xml.internal bind.v2.ContextFactory
    at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:445)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:587)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
    at javax.xml.bind.ServiceLoaderUtil.nullSafeLoadClass(ServiceLoaderUtil.java:122)
    at javax.xml.bind.ServiceLoaderUtil.safeLoadClass(ServiceLoaderUtil.java:155)
    at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:276)

So this tells me that jaxb-runtime.jar is on the classpath, but for some reason, when the jaxb code goes to look for the correct Context Factory, it doesn't find the service lookup info, and just defaults to the default. (notice the .internal.)

If i add a jaxb.properties file and force it to pick my class, then it just CNFEs on com.sun.xml.bind.v2.ContextFactory

It's like there are two classloaders in play or something.

Anyone have any idea as to how to debug further?


Solution

  • I just encountered the same issue whilst trying to upgrade a project from Java 8 to Java 17.

    Thanks to this I found what appears to be some very odd behaviour by the JAXB runtime to do with class loaders.

    As a work-around I have wrapped all my JAXBContext.newInstance calls like this:

    public interface Util {
    
    // TODO see here: https://stackoverflow.com/questions/64979229/eclipse-osgi-java-11-jaxb-and-the-classloader
    //  - without this the classloader breaks for Java >=9
    default <T> Unmarshaller getUnmarshaller(Class<T> cl) throws JAXBException {
        Thread thread = Thread.currentThread();
        ClassLoader classLoader = thread.getContextClassLoader();
        try {
            thread.setContextClassLoader(getClass().getClassLoader());
            JAXBContext ctx = JAXBContext.newInstance(cl);
            return ctx.createUnmarshaller();
        } finally {
            thread.setContextClassLoader(classLoader);
        }
    }
    

    Also, as described here you need a file src/main/resources/jaxb.properties:

    javax.xml.bind.JAXBContextFactory=com.sun.xml.bind.v2.ContextFactory
    

    I don't pretend to understand why this is necessary. It would be great if someone could explain, and perhaps suggest a better way to fix it.

    HTH