jakartaee / jakartaee/jaxb-api

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

Open
#308 3 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Java
Stars
79
Forks
48
Avg merge
7h 12m
Merged PRs (30d)
2

Description

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


4.0.0

org.example
temp-jaxb-test
1.0-SNAPSHOT


21
UTF-8




jakarta.xml.bind
jakarta.xml.bind-api
4.0.2


org.glassfish.jaxb
jaxb-runtime
4.0.5



```
- `/src/main/java/org/example/Main.java`
```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](https://www.jenkins.io/doc/developer/plugin-development/dependencies-and-class-loading/#context-class-loaders) 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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the failure with the supplied pom.xml and src/main/java/org/example/Main.java, then inspect ContextFinder and ServiceLoaderUtil around the stack trace. Determine the intended class-loader behavior and verify that JAXBContext.newInstance succeeds when the thread context class loader contains no JAXB jars, without breaking the existing lookup paths.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.