How to switch from maven-bundle-plugin to bnd-maven-plugin

Overview

For many years the Apache Felix maven-bundle-plugin was the standard plugin used for building OSGi bundle projects in Maven. However, this has changed since the bnd project (which is also used as library inside maven-bundle-plugin) introduced their own Maven plugin, the bnd-maven-plugin. This plugin quickly catched up with most features that maven-bundle-plugin provides, and is always released immediately together with new bnd versions. Thus it is recommended to switch maven projects to the bnd-maven-plugin (also there is no urgent need to do this for stable projects).

This article describes the required steps. It is mainly focused on projects inheriting from the wcm.io AEM Global Parent POM, which contains all basic configuration suitable for Maven bundle projects using either maven-bundle-plugin or bnd-maven-plugin.

Migration Steps

Step 1: Update aem-global-parent

Update to aem-global-parent 1.4.0 or later - this version contains the base configuration of the bnd-maven-plugin.

Step 2: Update packaging

Unlike the maven-bundle-plugin the bnd-maven-plugin does no longer use the “bundle” packaging type - change the packaging type to “jar”.

Step 3: Replace Maven Plugins

Replace the Maven Bundle Plugin

1 2 3 4 <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> </plugin>

with:

1 2 3 4 5 6 7 8 9 10 11 12 13 <plugin> <groupId>biz.aQute.bnd</groupId> <artifactId>bnd-maven-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile> </archive> </configuration> </plugin>

The presence of the maven-bundle-plugin was optional if you had not the need to customize anything. If your Maven project is a bundle project you have to add the bnd-maven-plugin.

The maven-jar-plugin customization is only required due to this unresolved issue: MJAR-193. It cannot be defined in the parent POM as this would break the build of non-bundle Maven projects.

If you want to activate the Semantic Versioning check for the exported API of your bundles, additionally enable the bnd-baseline-maven-plugin. This is usually only required if your bundle contains reusable code that is used by other bundles/projects, not for “normal” AEM project bundles.

1 2 3 4 <plugin> <groupId>biz.aQute.bnd</groupId> <artifactId>bnd-baseline-maven-plugin</artifactId> </plugin>

Step 4: Migrate Bundle Header Instructions

If you have defined custom bundle header instructions using the <instructions> configuration configuration option of the maven-bundle-plugin, you have to migrate them to the different syntax of the bnd-maven-plugin.

Example from maven-bundle-plugin:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <configuration> <instructions> <Sling-Initial-Content> SLING-INF/app-root;overwrite:=true;ignoreImportProviders:=xml;path:=/apps/wcm-io/wcm/commons </Sling-Initial-Content> <Sling-Model-Packages> io.wcm.wcm.commons </Sling-Model-Packages> <Sling-Namespaces>wcmio=http://wcm.io/ns</Sling-Namespaces> <Import-Package> <!-- For build compatibility with Java 11 --> javax.annotation;version="[0.0,2)", * </Import-Package> </instructions> </configuration> </plugin>

Translates with bnd-maven-plugin to:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <plugin> <groupId>biz.aQute.bnd</groupId> <artifactId>bnd-maven-plugin</artifactId> <configuration> <bnd> Sling-Initial-Content: SLING-INF/app-root;overwrite:=true;ignoreImportProviders:=xml;path:=/apps/wcm-io/wcm/commons Sling-Model-Packages: io.wcm.wcm.commons Sling-Namespaces: wcmio=http://wcm.io/ns Import-Package: \ <!-- For build compatibility with Java 11 -->\ javax.annotation;version="[0.0,2)",\ * </bnd> </configuration> </plugin>

Notes for this translation:

  • We recommend to keep the Instructions in the pom.xml instead of using a separate bnd.bnd file. Defining it in the POM allows to inherit (and merge) settings via POM parent hierarchies, and allows to add XML comments (event inside a line split over multiple lines).

  • The <bnd> configuration option does not support nested sub elements. Instead, it’s just a text file using the notation <Instruction>: <Value>.

  • You can split a long line into multiple lines by putting \ on the end of each line (also on lines with XML comments!).

  • See the bnd documentation for a full index of bnd instructions.

  • It’s usually not required to put <![CDATA[…]]> around your bnd instructions. You might have to escape characters special to XML then, though.

Step 5: Verify Package Exports

The maven-bundle-plugin has a default behavior that always export all packages of the project that do not include “impl” or “internal” somewhere in the package name. This is no longer the case for the bnd-maven-plugin! The bnd-maven-plugin exports only packages that contain a package-info.java with an OSGi @Export annotation or an OSGi @Version annotation (the latter is a special configuration defined in aem-global-parent). This is a useful default setting for shared libraries that should export only those packages really required.

For “normal” AEM application bundles it may be too cumbersome to export each package explicit (especially as all packages containing Sling Models have to be exported to be accessible from HTL scripts). Also it would complicate the migration of existing projects relying on the old behavior. For those projects you can add this bnd instruction to your project’s POM file - example:

1 2 3 <!-- Export all non-internal packages by default --> Export-Package: !*.impl.*,!*.internal.*,\ io.wcm.samples.*

Replace io.wcm.samples.* with the topmost package from your project.

Step 6: Check for usage of build-helper-maven-plugin with add-resources goal

In wcm.io-based projects it was usual to apply special resource filtering to JSON files describing AEM client libraries to replace the “long cache key” with the current bundle version and SCM build number at build time. Usage example:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <!-- Apply maven filtering to clientlib definitions (cache key) --> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>build-helper-maven-plugin</artifactId> <executions> <execution> <goals> <goal>add-resource</goal> </goals> <phase>process-resources</phase> <configuration> <resources> <resource> <directory>src/main/webapp</directory> <targetPath>SLING-INF</targetPath> <filtering>true</filtering> <includes> <include>clientlibs-root/*.json</include> </includes> </resource> </resources> </configuration> </execution> </executions> </plugin>

You have to remove this definition completely and replace it with a “resources” definition in the “build” section - example:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <resources> <!-- copy webapp resources to SLING-INF/app-root --> <resource> <directory>src/main/webapp</directory> <targetPath>SLING-INF</targetPath> <filtering>false</filtering> </resource> <!-- Apply maven filtering to clientlib definitions (cache key) --> <resource> <directory>src/main/webapp</directory> <targetPath>SLING-INF</targetPath> <filtering>true</filtering> <includes> <include>clientlibs-root/*.json</include> </includes> </resource> </resources>

Unfortunately you have to copy the existing resource definitions from the aem-global-parent POM that are relevant for your module as they are not inherited/merged by Maven automatically.

Step 7: Check for optional dependencies

Watch out for maven dependencies in your POM that are declared as “optional”. The maven-bundle-plugin marked the package imports for those automatically as “resolution=optional” - this is not the case, you have to list mark packages yourself - example:

1 2 3 4 Import-Package: \ <!-- optional dependency -->\ io.wcm.sling.commons.caservice;resolution:=optional,\ *

Optional Steps

Embedding JARs/Classes in the bundle

The bnd-maven-plugin has no direct counterpart for the Embed-Dependency feature of the maven-bundle-plugin which is used to included 3rdparty JARs into the bundle (inline or as JAR file).

To inline a dependency you can use the -conditionalpackage instruction.

If you want to include a dependency as JAR file you can copy it using maven-dependency-plugin and include it using Include-Resource and Bundle-ClassPath instruction, see example.

Legacy Felix SCR Annotations

The bnd-maven-plugin configured in aem-global-parent does no longer support the deprecated Felix SCR Annotations by default. It is recommended to update your project to make use of the standard OSGI annotations instead. If this is not possible you can add support for them for the bnd-maven-plugin:

Add this dependencies to the bnd-maven-plugin:

1 2 3 4 5 6 7 8 9 10 <dependency> <groupId>org.apache.felix</groupId> <artifactId>org.apache.felix.scr.bnd</artifactId> <version>1.9.4</version> </dependency> <dependency> <groupId>org.apache.felix</groupId> <artifactId>org.apache.felix.scr.annotations</artifactId> <version>1.12.0</version> </dependency>

And add this bnd Instruction to activate the Felix SCR bnd plugin:

1 2 <!-- Support parsing of Felix SCR annotations through the felix.scr.bnd plugin --> -plugin.felixscrannotations: org.apache.felix.scrplugin.bnd.SCRDescriptorBndPlugin;destdir=target/classes