Eclipse RCP, Java 11, JAXB

With Java 11 several packages have been removed from the JRE itself, like JAXB. This means if you use JAXB in your Java application, you need to add the necessary bundles to your runtime. In an OSGi application this gets quite complicated as you typically only declare a dependency to the API. The JAXB API and the JAXB implementation are separated, which is typically a good design. But the JAXBContext in the API bundle loads the implementation, which means the API has to know the implementation. This is causing class loading issues that become hard to solve.

This topic is of course not new and there are already some explanations like this blog post or this topic on the equinox-dev mailing list. But as it still took me a while to get it working, I write this blog post to share my findings with others. And of course to persist my findings in my “external memory” if I need it in the future again. 🙂

The first step is to add the necessary bundles to your target platform. You can either consume it from an Eclipse p2 Update Site or directly from a Maven repository using the m2e PDE Integration feature.

Note:
If you open the .target file with the Generic Text Editor, you can simply paste one of the below blocks and then resolve the target definition, instead of using the Target Editor.

Using an Eclipse p2 Update Site you can add the necessary dependencies by adding the following block to your target definition.

<location includeAllPlatforms="true" includeConfigurePhase="false" includeMode="slicer" includeSource="true" type="InstallableUnit">
  <repository location="https://download.eclipse.org/releases/2020-12/"/>
    <unit id="jakarta.xml.bind" version="2.3.3.v20201118-1818"/>
    <unit id="com.sun.xml.bind" version="2.3.3.v20201118-1818"/>
    <unit id="javax.activation" version="1.2.2.v20201119-1642"/>
</location>

For consuming the libraries directly from a Maven repository you can add the following block if you have the m2e PDE Integration feature installed. This way you could even use newer versions that are not yet available via p2 update site.

<location includeDependencyScope="compile" includeSource="true" missingManifest="generate" type="Maven">
  <groupId>com.sun.xml.bind</groupId>
  <artifactId>jaxb-impl</artifactId>
  <version>2.3.3</version>
  <type>jar</type>
</location>
<location includeDependencyScope="compile" includeSource="true" missingManifest="generate" type="Maven">
  <groupId>jakarta.xml.bind</groupId>
  <artifactId>jakarta.xml.bind-api</artifactId>
  <version>2.3.3</version>
  <type>jar</type>
</location>

Note:
If you don’t have a JavaSE-1.8 mapped in your Eclipse IDE, or your bundle has a JavaSE-11 or higher set as Execution Environment, you need to specify the version constraint to the Import-Package statements to make PDE happy. Otherwise you will see some strange errors.

Note:
The Bundle-SymbolicName of the required bundles in Maven Central is different to the re-bundled versions in the Eclipse p2 Update Site. This needs to be kept in mind when including the bundles to the product. I will use the symbolic names of the bundles from Maven Central in the further sections.

Once the bundles are available in the target platform there are different ways to make JAXB work with Java 11 in your OSGi / Eclipse application.

Variant 1: Modify bundle and code

This is the variant that is most often described.

  1. Add the package com.sun.xml.bind.v2 to the imported packages of the bundle that uses JAXB
  2. Create the JAXBContext by using the classloader of the model object
    JAXBContext context =
    JAXBContext.newInstance(
    MyClass.class.getPackageName(),
    MyClass.class.getClassLoader());
  3. Place a jaxb.index file in the package that contains the model classes. This file contains the simple class names of all JAXB mapped classes. For more information about the format of this file, have a look at the javadoc of the JAXBContext#newInstance(String, ClassLoader) method.

The following bundles need to be added to the product in order to make JAXB work with Java 11 in OSGi:

  • jakarta.activation-api
  • jakarta.xml.bind-api
  • com.sun.xml.bind.jaxb-impl

The downside of this variant is obviously that you have to modify code and you have to add a dependency to a JAXB implementation in all places where JAXB is used. In case third-party-libraries are part of your product that you don’t have under your control, this solution is probably not suitable. And you can also not exchange the JAXB implementation easily with this approach.

Variant 2: jakarta.xml.bind-api fragment

In this variant you create a fragment named jaxb.impl.binding to the jakarta.xml.bind-api bundle that adds the package com.sun.xml.bind.v2 to the imported packages.

  • Create a Fragment Project
  • Use jakarta.xml.bind-api as the Fragment-Host
  • Add com.sun.xml.bind.v2 to the Import-Package manifest header

The resulting MANIFEST.MF should look similar to the following snippet:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: JAXB Impl Binding
Bundle-SymbolicName: jaxb.impl.binding
Bundle-Version: 1.0.0.qualifier
Fragment-Host: jakarta.xml.bind-api;bundle-version="2.3.3"
Automatic-Module-Name: jaxb.impl.binding
Bundle-RequiredExecutionEnvironment: JavaSE-11
Import-Package: com.sun.xml.bind.v2

The following bundles need to be added to the product in order to make JAXB work with Java 11 in OSGi:

  • jakarta.activation-api
  • jakarta.xml.bind-api
  • com.sun.xml.bind.jaxb-impl
  • jaxb.impl.binding

This variant seems to me the most comfortable one. There are no modifications required in the existing bundles and the dependency to the JAXB implementation is encapsulated in a fragment, which makes it easy to exchange if needed.

Variant 3: system.bundle fragment

With this variant you add the necessary bundles to the classloader the framework is started with.
Using bndtools this can be done via the -runpath instruction. The Equinox launcher does not know such an instruction. For an Eclipse RCP application you need to create system.bundle fragment. Such a fragment contains the necessary jar files and exports the packages of the wrapped jars.

  • Download the required jar files, e.g. from Maven Central, and place them in a folder named lib in the fragment project
    • jakarta.activation-api-1.2.2.jar
    • jakarta.xml.bind-api-2.3.3.jar
    • jaxb-impl-2.3.3.jar
  • Specify the Bundle-ClassPath manifest header to add the jars to the bundle classpath
  • Specify the Fragment-Host manifest header so the fragment is added to the system.bundle
  • Add the packages of the included libraries to the Export-Packages manifest header

The resulting MANIFEST.MF should look similar to the following snippet:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Extension
Bundle-SymbolicName: jaxb.extension
Bundle-Version: 1.0.0.qualifier
Fragment-Host: system.bundle; extension:=framework
Automatic-Module-Name: jaxb.extension
Bundle-RequiredExecutionEnvironment: JavaSE-11
Bundle-ClassPath: lib/jakarta.activation-api-1.2.2.jar,
 lib/jakarta.xml.bind-api-2.3.3.jar,
 lib/jaxb-impl-2.3.3.jar,
 .
Export-Package: com.sun.istack,
 com.sun.istack.localization,
 com.sun.istack.logging,
 com.sun.xml.bind,
 com.sun.xml.bind.annotation,
 com.sun.xml.bind.api,
 com.sun.xml.bind.api.impl,
 com.sun.xml.bind.marshaller,
 com.sun.xml.bind.unmarshaller,
 com.sun.xml.bind.util,
 com.sun.xml.bind.v2,
 com.sun.xml.bind.v2.bytecode,
 com.sun.xml.bind.v2.model.annotation,
 com.sun.xml.bind.v2.model.core,
 com.sun.xml.bind.v2.model.impl,
 com.sun.xml.bind.v2.model.nav,
 com.sun.xml.bind.v2.model.runtime,
 com.sun.xml.bind.v2.model.util,
 com.sun.xml.bind.v2.runtime,
 com.sun.xml.bind.v2.runtime.output,
 com.sun.xml.bind.v2.runtime.property,
 com.sun.xml.bind.v2.runtime.reflect,
 com.sun.xml.bind.v2.runtime.reflect.opt,
 com.sun.xml.bind.v2.runtime.unmarshaller,
 com.sun.xml.bind.v2.schemagen,
 com.sun.xml.bind.v2.schemagen.episode,
 com.sun.xml.bind.v2.schemagen.xmlschema,
 com.sun.xml.bind.v2.util,
 com.sun.xml.txw2,
 com.sun.xml.txw2.annotation,
 com.sun.xml.txw2.output,
 javax.activation,
 javax.xml.bind,
 javax.xml.bind.annotation,
 javax.xml.bind.annotation.adapters,
 javax.xml.bind.attachment,
 javax.xml.bind.helpers,
 javax.xml.bind.util

If you add this system.bundle fragment to the product, JAXB works the same way it did with Java 8.

This variant has the downside that you have to manage the JAXB libraries that are wrapped by the system.bundle fragment yourself, instead of simply consuming it from a repository.

Conclusion

For me the creation of a jakarta.xml.bind-api fragment as shown in Variant 2 seems to be the most comfortable variant. At least it worked in my scenarios, and also the build using Tycho 2.2 and the resulting Eclipse RCP product worked.

If you need to support Java 8 and Java 11 with your product at the same time, you should consider specifying the binding fragment as multi-release jar as explained in this blog post. Further information about multi-release jars can be found here:

If you see any issues with the jakarta.xml.bind-api fragment approach that I have not identified yet, please let me know. Maybe I am missing something important that was not covered by my tests.

About Dirk Fauth

Dirk Fauth is a Software Architect for Rich Client Systems working for the Robert Bosch GmbH in Stuttgart and a lecturer in Java basics for the Baden-Wuerttemberg Cooperative State University (DHBW). He is active in developing, teaching and talking about OSGi, Eclipse RCP applications and Eclipse related technologies. He is project lead of the Nebula NatTable project, Eclipse Platform committer and also a committer and contributor to several other Eclipse projects. (Twitter: @fipro78)
This entry was posted in Dirk Fauth, Eclipse, Java, OSGi. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *