Eclipse RCP, Java 11, JAXB

7 minute read

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"/>
    <unit id="javax.xml" version="1.3.4.v201005080400"/>
</location>

Note: The jakarta.xml.bind bundle from Orbit is a re-bundled version of the original bundle in Maven Central and unfortunately specifies a version constraint on some javax.xml packages. As the Java runtime does not specify a version on the javax.xml packages, the configuration will fail to resolve. To solve this you need to add the javax.xml bundle to your target definition and the product configuration.

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.

Note:
There still might be issues at runtime when trying to execute JAXB code. In such cases try to change to Import-Package statement to either an import with a version or a DynamicImport-Package:

Import-Package: com.sun.xml.bind.v2;version="2.3.3"

or

DynamicImport-Package: com.sun.xml.bind.\*

If even this does not solve the issue, try to start the application clean to ensure that no bundle caching issue exists!

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](https://bnd.bndtools.org/instructions/runpath.html) 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.

Update October 2022

At the OSGi Summit 2022 I learned that there is another variant that is even more comfortable than using the jakarta.xml.bind-api fragment as shown in Variant 2.

Variant 4: org.glassfish.hk2.osgi-resource-locator

In this variant you simply add the bundle org.glassfish.hk2.osgi-resource-locator to your application. This way the ServiceLocator is activated which is publishing the Java services that are included in the com.sun.xml.bind.jaxb-impl bundle.

To use the org.glassfish.hk2.osgi-resource-locator it needs first to be added to the target definition. This can be done either via the following Maven location:

<location includeDependencyDepth="none" includeSource="true" missingManifest="generate" type="Maven">
  <dependencies>
    <dependency>
      <groupId>org.glassfish.hk2</groupId>
      <artifactId>osgi-resource-locator</artifactId>
      <version>1.0.3</version>
      <type>jar</type>
    </dependency>
  </dependencies>
</location>

Or you consume it from the Eclipse Orbit with the following p2 location:

<location includeAllPlatforms="false" includeConfigurePhase="true" includeMode="planner" includeSource="true" type="InstallableUnit">
  <repository location="https://download.eclipse.org/tools/orbit/downloads/drops/R20220830213456/repository"/>
  <unit id="org.glassfish.hk2.osgi-resource-locator" version="1.0.3.v20200509-0149"/>
</location>

Once this is done you only need to ensure that the following bundles are part of your application:

  • jakarta.activation-api
  • jakarta.xml.bind-api
  • com.sun.xml.bind.jaxb-impl
  • org.glassfish.hk2.osgi-resource-locator

Note:
The usage of the org.glassfish.hk2.osgi-resource-locator ServiceLocator mechanism does not automatically work with every JAXB implementation. The Eclipse MOXy implementation for example does not contain the necessary service definition in the META-INF folder until 4.0.0. But this seems to be fixed in the newest 4.0.0 version of MOXy, so since that version the org.glassfish.hk2.osgi-resource-locator approach also work here. For cases where org.glassfish.hk2.osgi-resource-locator is not working to resolve the classloading issues with JAXB implementations, the fragment approach described in Variant 2 is working.

The examples for verification of the GlassFish HK2 and the fragment approach are available at GitHub.

Thanks to Mark Hoffmann (Twitter: @him7791) for sharing his experience at the OSGi Summit 2022!

Updated: