OSGi Dependencies

Source code typically has dependencies on external libraries. In OSGi this can be implemented in a number of ways.

Maven is a key component to resolve library dependencies. It is no different with OSGi containers. The container import and export functions should be configured aligned with those in Maven.

OSGi libraries

Some Java libraries are already OSGi compliant and can therefore make use of the Import-Packages and Export-Packages mechanisms to resolve the dependencies.

Global

Libraries can be added to the global class loader (Tomcat’s lib and lib/ext, for example), making it possible to access these classes from anywhere in the code, including within the OSGi container.

Global jars, however, create problems. Not only do they need to be global, but all of their dependencies must be global too. Also, they can only have a single versions, so different versions cannot be supported.

It is not advisable to deploy libraries to the global load balancer under normal conditions.

Option 3 - Make An Uber Module

Just like uber jars, uber modules will have all of the dependent classes exploded out of their original jars and are available within the module jar. This is called inline.

This is actually quite easy to do using Gradle and BND.

In your build.gradle file, you should declare your runtime dependencies just as you did for Option 2.

To make the uber module, you also need to include the resources in your bnd.bnd file:

Include-Resource: @itext-1.4.8.jar So here you include the name of the dependent jar, usually you can see what it is when Gradle is downloading the dependency or by browsing your maven repository.

Note that you must also include any dependent jars in your include statement. For example, iText 2.0.8 has dependencies on BouncyCastle mail and prov, so those would need to be added:

Include-Resource: @itext-2.0.8.jar,@bcmail-138.jar,@bcprov-138.jar You may need to add these as runtime dependencies so Gradle will have them available for inclusion.

If you use a zip tool to crack open your module jar, you’ll see that all of the individual jars have been exploded and all classes are in the jar.

Option 4 - Include the Jars in the Module

The last option is to include the jars in the module itself, not as an uber module, but just containing the jar files within the module jar.

Similar to option 2 and 3, you will declare your runtime dependencies in the build.gradle file.

The bulk of the work is going to be done in the bnd.bnd file.

First you need to define the Bundle-ClassPath attribute to include classes in the module jar but also the extra dependency jars. In the example below, I’m indicating that my iText jar will be in a lib directory within the module jar:

Bundle-ClassPath:\ .,\ lib/itext.jar Rather than use the Include-Resource header, we’re going to use the -includeresource directive to pull the jars into the bundle:

-includeresource:\ lib/itext.jar=itext-1.4.8.jar In this format we’re saying that lib/itext.jar will be pulled in from itext-1.4.8.jar (which is one of our runtime dependencies so Gradle will have it available for the build).

This format also supports the use of wildcards so you can leave version selection to the build.gradle file. Here’s an example for including any version of commons-lang:

-includeresource:\ lib/itext.jar=itext-1.4.8.jar,\ lib/commons-lang.jar=commons-lang-[0-9]*.jar If you use a zip tool to crack open your module jar, you’ll find there are jars now in the bundle under the lib directory.

Resolving ClassNotFoundException and NoClassDefFoundError

In OSGi there is no global classpath and instead each bundle has its own isolated class loader. The loader inside each bundle can only load classes that are either inside the bundle, or explicitly imported from another bundle.

In non-OSGi environments ClassNotFoundException is thrown when looking up a class that isn’t on the classpath or using an invalid name to look up a class that isn’t on the runtime classpath.

NoClassDefFoundError occurs when a compiled class references another class that isn’t on the runtime classpath.

In OSGi environments, however, there are additional cases where a ClassNotFoundException or NoClassDefFoundError can occur. The class in error could;

  1. belongs to a module dependency that is an OSGi module

  2. belongs to a module dependency that is not an OSGi module.

  3. belongs to a global library

  4. belongs to a Java runtime package.

Each case is discussed below.

Case 1: The Missing Class Belongs to an OSGi Module

In this case, there are two possible causes:

The module doesn’t import the class’s package

For a module to consume another module’s exported class, the consuming module must import the exported package that contains the class. To do this, add an Import-Package header in the consuming module. If the consuming module tries to access the class without importing it, a ClassNotFoundException or NoClassDefFoundError occurs.

In the consuming module, make sure the correct package is then imported. First check the package name. If the package import is correct but the exception or error is still present, the class might no longer exist in the package.

The class no longer exists in the imported package

In OSGi runtime environments, modules can change and come and go. If another module’s class is referenced but the developer has since removed it, a NoClassDefFoundError or ClassNotFoundException occurs. Semantic Versioning guards against this scenario: removing a class from an exported package constitutes a new major version for that package. Neglecting to increment the package’s major version breaks dependent modules.

For example, say a module that consumes the class com.foo.Bar specifies the package import com.foo;version=[1.0.0, 2.0.0). The module uses com.foo versions from 1.0.0 up to (but not including) 2.0.0. The first part of the version number (the 1 in 1.0.0) represents the major version. The consuming module doesn’t expect any major breaking changes, like a class removal. Removing com.foo.Bar from com.foo without incrementing the package to a new major version (e.g., 2.0.0) causes a ClassNotFoundException or NoClassDefFoundError when other modules look up or reference that class.

If the class no longer exists in the package the following actions can be taken;

  1. Adapt to the new API. To learn how to do this, read the package’s/module’s Javadoc, release notes, and or formal documentation, contact the author, or search forums.

  2. Revert to the module version to the one used previously.

Do what you think is best to get your module working properly.

Now you know how to resolve common situations involving ClassNotFoundException or NoClassDefFoundError. For additional information on NoClassDefFoundError, see OSGi Enroute’s article What is NoClassDefFoundError?.

Case 2: The Missing Class Doesn’t Belong to an OSGi Module In this case, you have two options:

Convert the dependency into an OSGi module so it can export the missing class. Converting a non-OSGi JAR file dependency into an OSGi module that you can deploy alongside your application is the ideal solution, so it should be your first choice.

Embed the dependency in your module by embedding the dependency JAR file’s packages as private packages in your module. If you want to embed a non-OSGi JAR file in your application, see the tutorial Adding Third Party Libraries to a Module.

Case 3: The Missing Class Belongs to a Global Library In this case, you can configure Liferay DXP so the OSGi system module exports the missing class’s package. Then your module can import it. You should NOT, however, undertake this lightly. If Liferay intended to make a global library available for use by developers, the system module would already export this library! Still, if you must access a global library that’s not currently exported and can’t think of any other solution, you can consider adding the required package for export by the system module. There are two ways to do this:

In your portal-ext.properties file, use the property module.framework.system.packages.extra to specify the packages to export.

If the package you need is from a Liferay DXP JAR, you might be able to add the module to the list of exported packages in [LIFERAY_HOME]/osgi/core/com.liferay.portal.bootstrap.jar’s META-INF/system.packages.extra.bnd file. Try this option only if the first option doesn’t work.

If the package you need is from a Liferay DXP module, (i.e., it’s NOT from a global library), you can add the package to that module’s bnd.bnd exports. You should NOT, however, undertake this lightly. The package would already be be exported if Liferay intended for it to be available.

Case 4: The Missing Class Belongs to a Java Runtime Package rt.jar (the JRE library) has non-public packages. If your module imports one of them, configure Liferay DXP’s system bundle to export the package to the module framework.

Add the current module.framework.system.packages.extra property setting to a [LIFERAY_HOME]/portal-ext.properties file. Your server’s current setting is in the Liferay DXP web application’s /WEB-INF/lib/portal-impl.jar/portal.properties file.

In your portal-ext.properties file, append the required Java runtime package to the end of the module.framework.system.packages.extra property’s package list.

Restart your server.

Differences between ClassNotFoundException and NoClassDefFoundError

Both the ClassNotFoundException and NoClassDefFoundError may be seen when using Java libraries that try and load classes reflectively. This typically involves a Java library using Class.forName() to load a class.

ClassNotFoundException

A ClassNotFoundException is generated by a call to Class.forName() with a String that contains a class not available on the bundle’s class path. Unless the bundle has a Import-Package or Require-Bundle for the package in question (or a DynamicImport-Package), the runtime will not be able to find the appropriate .class.

NoClassDefFoundError

A NoClassDefFoundError is generated when a class has been found, but one of its dependencies (typically, that involved in a static initialiser block) cannot be. For example, if class A refers to B, and B refers to C, then a client looking up A may work, but B (or C) could be missing. This would generate the error message NoClassDefFoundError: A.