JAXB puzzles with Java 11

JAXB, which means Java Architecture for XML Binding, is a library that can be used to persist Java objects as XML documents. XML is a text-based format that can be used to represent complex and hierarchical data. JAXB can unmarshal a XML document directly into objects whose classes are defined by the developer, rather than letting the developer parse a generic abstract representation such as the Document Object Model (DOM) into domain-specific
objects. JAXB can also marshal objects into XML documents. This can be used to persist data in XML, a text-based format independent of the platform, as opposed to some alternatives like Java object serialization.

For an introduction to JAXB, see for example Guide to JAXB.

Object mapping is not specific to JAXB. The Jackson library can be used to parse JSON documents directly into objects, while SnakeYaml can do the same for YAML. These libraries are not essential but very handy. Instead of just parsing the XML, JSON or YAML files into an abstract tree that then needs to be traversed by tedious and repetitive code, they directly map the data to business objects, performing some degrees of validation in the process.

However, these libraries are quite problematic when they stop working after upgrading to a newer Java version. This is exactly with happened with JAXB past Java 8. Fortunately, solutions exist, but they don’t always work.

This post summarizes the known problems and proposes solutions. We first start with the classic dependency issues due to removal of JAXB from JavaSE, then go on with a less common issue that can arise in applications involving multiple class loaders.

Somewhat know but not fully solved problem

There are many, sometimes misleading, posts and forum questions about JAXB. Here are a couple of examples:

These posts lead me to believe not enough effort was spent in addressing the issue. This could be explained by many developers moving off XML, in favor of other formats like JSON or YAML. XML is a bit too verbose and many parsers must be explicitly configured to disable external entities to avoid some security vulnerabilities. JSON is a lot simpler format, but it doesn’t support comments by default. YAML is a bit less verbose than JSON and also supports comments, but its indentation-based syntax can be misleading.

In our case, we were stuck with XML-based projects our component had to continue loading. Migrating to JSON or YAML would break backward compatibility and providing migration tools would have been as tedious as parsing the XML documents without JAXB. Maybe a migration tool would be easier to write in Python, but such an offline tool would create complexity. If we have to switch project format, the Java code needs to be able to load the old XML-based projects and the new ones, no offline tool to convert XML to something else.

Compilation errors due to JAXBContext not found

The first thing that occurs after migrating a JAXB-enabled program from Java 8 to some newer versions is a compilation error because JAXBContext cannot be found anymore. This is because JAXB got turned into a module in Java 9 and it is not on the module path by default. Although this can be solved, it is better to explicitly add a JAXB library as a third party dependency since JAXB got removed completely from Java 11.

Besides the JAXB-API itself, the program needs an implementation. The most common JAXB implementation is Glassfish.

Here is an example of Maven dependencies for JAXB.

<dependency>
    <groupId>jakarta.xml.bind</groupId>
    <artifactId>jakarta.xml.bind-api</artifactId>
    <version>2.3.3</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jaxb</groupId>
    <artifactId>jaxb-runtime</artifactId>
    <version>2.3.6</version>
    <scope>runtime</scope>
</dependency>

There are more recent versions of JAXB, but they change the package names of the classes. The 2.x is the closest to what was provided in Java 8.

Setting the scope to runtime for jaxb-runtime is not strictly necessary, but is a good idea to help IDEs proposing meaningful code completion. No code should directly refer to JAXB implementation classes. Code should only interact with JAXB through its public API. This allows the implementation to be switched if need be.

javax.xml.bind.JAXBException: Implementation of JAXB-API has not been found on module path or classpath.

Main cause is the absence of a JAXB implementation on the class path. This can usually be solved by adding a dependency to your build file. See above for an example for Maven.

If the error persists, next step to investigate is obviously to look at the cause in the stack trace. There is a common but misleading cause:

Caused by: java.lang.ClassNotFoundException: com.sun.xml.internal.bind.v2.ContextFactory

Inspecting Glassfish JAXB implementation JAR, one can find that the implementation name is com.sun.xml.bind.v2.ContextFactory, without the « internal ». It is then tempting to believe there is an issue in JAXBContext.newInstance() using the wrong default factory class. However, this is not a problem for JAXB API 2.3.3 at least. I had to dig into the source code of JAXBContext to verify this.

JAXBContext.newInstance(), from JAXB-API, applies several strategies to search for a JAXB implementation, and it falls back on trying to load the default class present in Java 8 only if everything else fails. If JAXB implementation is present in the class path, JAXBContext.newInstance() should find it and not fall back on the missing Java 8 default.

Class loader intricacies

What if despite the fact you checked, double checked, triple checked, asked other developers to check, double check, triple check, that the JAXB dependencies are correct, and your program keeps complaining about JAXB implementation that cannot be found? Checking again like many forum posts suggest won’t help in some cases. Trying different versions of the JAXB artifacts could help, but doing that in a tentative blind way is likely to be of no help.

In a nutshell, you need to make sure JAXBContext.newInstance() is called at a place where the context class loader is correctly set up. Calling JAXBContext.newInstance() from a worker thread spawned by the Java Executor Service or ForkJoinPool can cause issues. It is better to construct the JAXBContext instances you need at the beginning of your program’s execution, store them in static variables and reuse them instead of recreating them again and again. Your program will benefit from an almost free performance boost when loading from or saving to XML, and you will be less likely to get JAXB issues. The JAXB problems, if any, will pop fast, right at application startup, rather than later on after the application receives requests.

Understanding the reason for this is not obvious and requires digging into Java class loaders. When a Java application requires a new class or resource, it uses a class loader to search for it. Most class loaders look for resources at well-defined locations named the class path. Locations can be directories, archive files (with .jar extension) or URLs to archive files (the contents will be downloaded as needed).

Simple applications started with java command line and running a main method from a class have a single class loader. This loader is created at startup, using a class path coming from the CLASSPATH environment variable or passed through the command line using the -cp option. Java applications started with -jar option also have a single class loader. If JAXB can be verified to be on the class path, the application should work correctly.

Problems arise when the Java Virtual Machine deals with multiple class loaders. Spring Boot applications can have more than one class loaders if they create multiple application contexts. Even running a Java program through the Maven Exec plugin results in a second class loader being created for the execution.

When there are multiple class loaders, a question should come up: how JAXBContext.newInstance() chooses a class loader to search for a JAXB implementation? There are unfortunately multiple possibilities, and this is not necessarily the one you think about.

  1. JAXBContext.newInstance() has some overloads accepting an explicit class loader. In that case, that class loader will be used to search for the implementation. But being in control of the class loader doesn’t necessarily mean you will know the correct one to pass. Moreover, not all forms of newInstance accept a class loader, e.g., the one taking XML classes to bind to doesn’t.
  2. Some developers, including me, could think that JAXBContext.newInstance() will use the class loader bound to JAXBContext. This class loader can be retrieved easily, using JAXBContext.class.getClassLoader(). If JAXB-API and JAXB implementation are on the same class path, the class loader that loaded JAXBContext should be suitable to find the implementation. But JAXBContext.newInstance() gets its class loader another way. See 3.
  3. Using the current thread’s context class loader. This can be retrieved using Thread.currentThread().getContextClassLoader(). It can be confirmed, checking at source code, that this is how JAXBContext.newInstance() locates the JAXB implementation.

Wrapper code such as the Maven Exec plugin or Spring Boot that creaes a custom class loader will take care of setting that class loader as the current thread’s context class loader, using Thread.currentThread().setContextClassLoader(). However, what if the wrapped code uses other threads?

If the wrapped code creates a Thread of its own, using new Thread(), the new thread will inherit the context class loader from its parent. However, it seems that things are different if threads from a pool are reused. Facilities such as the Executor Service and ForkJoinPool, offered by Java, can create threads, and it is not guaranteed these threads, that can be reused by multiple parent threads, will have the expected class loader. They may end up with the default class loader, and that class loader could be unable to locate JAXB implementation, unless it is baked into the Java standard library, like in Java 8.

This explains why a program that worked well with Java 8 starts to exhibit JAXB errors in Java 9 and onward.

Possible solutions

  1. As outlined above, the ideal solution is to make sure JAXBContext.newInstance() is only called from the main thread and the JAXBContext instances are cached and reused. JAXBContext is thred-safe; only the Marshaller and Unmarshaller created by JAXBContext are not thread-safe.
  2. If above fails, one quirky possibility is to temporarily set the context class loader to something that could locate JAXB implementation, then reset the class loader. Here is an example of this.
ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
JAXBContext jc;
try {
   Thread.currentThread().setContextClassLoader(JAXBContext.class.getClassLoader());
   jc = JAXBContext.newInstance(MyXMLObject.class);
} catch (JAXBException e} {
   // Do something, this is a checked exception.
   // Lazy developers sometimes do this and that's not ideal.
   // If you do this, at least pass the thrown exception as the cause.
   throw new RuntimeException("An error occurred", e);
} finally {
   Thread.currentThread().setContextClassLoader(oldContextClassLoader);
}

Another very bad solution may be to directly call ContextFactory.newInstance() method from the JAXB implementation. This is not great, because it ties your code to a specific implementation, but it is at least better than getting rid of JAXB.