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"/>
<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.
- Add the package
com.sun.xml.bind.v2
to the imported packages of the bundle that uses JAXB - Create the
JAXBContext
by using the classloader of the model objectJAXBContext context =
JAXBContext.newInstance(
MyClass.class.getPackageName(),
MyClass.class.getClassLoader());
- 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 theFragment-Host
- Add
com.sun.xml.bind.v2
to theImport-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 thesystem.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!