OSGi and Java Package Imports

Later versions of Liferay are using OSGi Core R7, and this can impact Java package imports...

Just a quick one today...

A community member reported getting an exception during module deployment:

INFO  [com.liferay.portal.kernel.deploy.auto.AutoDeployScanner][AutoDeployDir:238] 
    Processing com.liferay.report.jar
ERROR [fileinstall-directory-watcher][DirectoryWatcher:1195] Unable to start bundle: 
    file:/D:/worksp/liferay-ce-portal-7.4.3.8-ga8/osgi/modules/com.liferay.report.jar
com.liferay.portal.kernel.log.LogSanitizerException: 
    org.osgi.framework.BundleException: Could not resolve module: 
    com.liferay.report [1555]_  
    Unresolved requirement: Import-Package: java.util.Map_ [Sanitized]
  at org.eclipse.osgi.container.Module.start(Module.java:444) 
    ~[org.eclipse.osgi.jar:?]
  at org.eclipse.osgi.internal.framework.EquinoxBundle.start(EquinoxBundle.java:428) 
    ~[org.eclipse.osgi.jar:?]
  at com.liferay.portal.file.install.internal.DirectoryWatcher.
    _startBundle(DirectoryWatcher.java:1178) [bundleFile:?]
  at com.liferay.portal.file.install.internal.DirectoryWatcher.
    _startBundles(DirectoryWatcher.java:1211) [bundleFile:?]
  at com.liferay.portal.file.install.internal.DirectoryWatcher.
    _startAllBundles(DirectoryWatcher.java:1156) [bundleFile:?]
  at com.liferay.portal.file.install.internal.DirectoryWatcher.
    _process(DirectoryWatcher.java:1068) [bundleFile:?]
  at com.liferay.portal.file.install.internal.DirectoryWatcher.
    run(DirectoryWatcher.java:250) [bundleFile:?]

Although a lot is in this error, the important part is the Unresolved requirement: Import-Package: java.util.Map portion.

Kind of weird, right? The module is failing because it cannot find the java.util.Map package. Obviously, because Map is a class, not a package.

So the question was how was the code referencing Map such that BND was considering it to be a package?

Long story short, they were importing java.util.Map.Entry in a JSP file. In this form, BND was interpreting Map as part of the package and Entry was the class...

So there were two solutions to this problem:

1. Change the import. When you import java.util.Map, you will automatically get the Entry class, although in some cases you may need to refer to it as Map.Entry. Not a big deal, super easy to change and use in this way.

2. Add a new directive to the bnd.bnd file...

So this second option I didn't know about until I talked to my friend Ray Auge...

In later versions of Liferay, OSGi Core R7 is used, and there was a change to BND to start including Java packages in the Import-Package declaration. This change would allow for identifying cases where a Java package is being used but maybe not available in the Java runtime. Consider, for example, a Java 11 module that uses classes from the java.lang.module package; by adding java.lang.module to the Import-Package declaration, the module would be flagged by OSGi in an environment where the java.lang.module package was not available.

So while this might be good generally, it is probably not a problem that we as Liferay developers will contend with...

And besides, if importing java.util.Map.Entry causes us some deployment headaches, this feature may not be worth it...

Fortunately for us, there is a new BND directive that you can add into your bnd.bnd file:
     -noimportjava: true
This directive will have BND exclude any java.* imports in the Import-Package declaration.

By adding this directive, the community member can continue to import java.util.Map.Entry and leave the rest of the code unchanged (may be necessary in cases where you might not be able to change the code for one reason or another).

In fact, there is no harm to adding the -noimportjava: true directive to all of your bnd.bnd files, so feel free to do so.

You can read more about the -noimportjava directive here: https://bnd.bndtools.org/instructions/noimportjava.html