This Guide provides resources to assist developers of Rhino Deployable Units (DUs) to migrate those DUs to run on Rhino 3 (or later) on JDK11.

Rhino 3 and later run only on JDK 11.0.4 and later. There are considerable changes between JDK8 and JDK11 which may mean that deployable units which build and run on older JDK/Rhino platforms no longer build on JDK11 or run on Rhino 3.

Background resources

There are many resources on the web for changes between JDK8 and JDK11. https://blog.codefx.org/java/java-11-migration-guide/#Migrating-From-Java-8-To-Java-11 has been found to be particularly useful.

Quantifying issues before you start

Before you undertake a migration it is good to have some idea of how much effort will be involved.

The jdeps tool, new in JDK8 and enhanced subsequently, has an option --jdk-internals to scan jar files for references to JDK internals.

Using this tool on the .jar file(s) for your DU(s) can be valuable in determining what, if any, code will need to be rewritten to no longer access JDK internals.

Using this tool on the .jar file(s) for any third party libraries you deploy can be valuable in determining whether they will need upgrading.

For example:

$jdeps --jdk-internals netty-common-4.0.12.jar
netty-common-4.0.12.jar -> JDK removed internal API
netty-common-4.0.12.jar -> jdk.unsupported
   io.netty.util.internal.PlatformDependent0          -> sun.misc.Cleaner                                   JDK internal API (JDK removed internal API)
   io.netty.util.internal.PlatformDependent0          -> sun.misc.Unsafe                                    JDK internal API (jdk.unsupported)
   io.netty.util.internal.chmv8.ConcurrentHashMapV8   -> sun.misc.Unsafe                                    JDK internal API (jdk.unsupported)
   io.netty.util.internal.chmv8.ConcurrentHashMapV8$1 -> sun.misc.Unsafe                                    JDK internal API (jdk.unsupported)
   io.netty.util.internal.chmv8.ConcurrentHashMapV8$TreeBin -> sun.misc.Unsafe                                    JDK internal API (jdk.unsupported)
   io.netty.util.internal.chmv8.CountedCompleter      -> sun.misc.Unsafe                                    JDK internal API (jdk.unsupported)
   io.netty.util.internal.chmv8.CountedCompleter$1    -> sun.misc.Unsafe                                    JDK internal API (jdk.unsupported)
   io.netty.util.internal.chmv8.ForkJoinPool          -> sun.misc.Unsafe                                    JDK internal API (jdk.unsupported)
   io.netty.util.internal.chmv8.ForkJoinPool$2        -> sun.misc.Unsafe                                    JDK internal API (jdk.unsupported)
   io.netty.util.internal.chmv8.ForkJoinPool$WorkQueue -> sun.misc.Unsafe                                    JDK internal API (jdk.unsupported)
   io.netty.util.internal.chmv8.ForkJoinTask          -> sun.misc.Unsafe                                    JDK internal API (jdk.unsupported)
   io.netty.util.internal.chmv8.ForkJoinTask$1        -> sun.misc.Unsafe                                    JDK internal API (jdk.unsupported)
   io.netty.util.internal.chmv8.Striped64             -> sun.misc.Unsafe                                    JDK internal API (jdk.unsupported)
   io.netty.util.internal.chmv8.Striped64$1           -> sun.misc.Unsafe                                    JDK internal API (jdk.unsupported)
   io.netty.util.internal.chmv8.Striped64$Cell        -> sun.misc.Unsafe                                    JDK internal API (jdk.unsupported)
Warning: JDK internal APIs are unsupported and private to JDK implementation that are
subject to be removed or changed incompatibly and could break your application.
Please modify your code to eliminate dependence on any JDK internal APIs.
For the most recent update on JDK internal API replacements, please check:
https://wiki.openjdk.java.net/display/JDK8/Java+Dependency+Analysis+Tool
JDK Internal API                         Suggested Replacement
----------------                         ---------------------
sun.misc.Cleaner                         Use java.lang.ref.PhantomReference @since 1.2 or java.lang.ref.Cleaner @since 9
sun.misc.Unsafe                          See http://openjdk.java.net/jeps/260
Tip

Run this tool on the jars bundled inside your DU, not on the DU jar itself.

Java development environment

You will need a Java 11 JDK, version 11.0.4 or later. For security reasons it is strongly recommended that you use the latest JDK version from a supported vendor or the same JDK version used for the target production deployment. Your options for obtaining a JDK are:

  • The Java SE Development Kit from Oracle. (You cannot use this in production environments without a license from Oracle.)

  • The OpenJDK package in the CentOS, Red Hat, or Fedora package repositories: yum install java-11-openjdk-devel

  • AdoptOpenJDK on other Linux distributions. Select the HotSpot JVM, not Eclipse OpenJ9.

Note These JDK recommendations for development may differ from those supported by production Rhino installations. For guidance on production deployments, please see the Rhino Compatibility Guide.

If using Ant, version 1.10.5 or later is required.

Migration process

Starting with your leaf dependencies, work though the stages for each DU using JDK11:

build → deploy → run

Problems you will encounter

  • Use of (no longer visible) JDK internals.

    Solved by rewriting the code to not do that. Specific solutions depend on which part of the JDK internals was being used. The jdeps tool mentioned below will point out the new official way to achieve the same thing in many cases.

  • Use of (no longer visible) JDK internals by third party libraries.

    Solved by upgrading the third party libraries to later versions which do not have the problem.

  • API changes in upgraded third party libraries.

    Solved by rewriting code to build against the upgraded library’s API.

  • Removal of JAXB module from JDK

  • Removal of JavaBeans Activation Framework from the JDK

    Both of these problems were solved by building and deploying Library DUs to provide the removed libraries, and referencing those libraries in the SLEE deployment descriptors for the DUs that used them.

Note

For more information on solutions for problems encountered, see common problems

Notices

Copyright © 2024 Microsoft. All rights reserved

This manual is issued on a controlled basis to a specific person on the understanding that no part of the Metaswitch Networks product code or documentation (including this manual) will be copied or distributed without prior agreement in writing from Metaswitch Networks.

Metaswitch Networks reserves the right to, without notice, modify or revise all or part of this document and/or change product features or specifications and shall not be responsible for any loss, cost, or damage, including consequential damage, caused by reliance on these materials.

Metaswitch and the Metaswitch logo are trademarks of Metaswitch Networks. Other brands and products referenced herein are the trademarks or registered trademarks of their respective holders.

Common Problems

Below are troubleshooting steps — problems and solutions — for common issues encountered during the migration to JDK11 with Rhino DUs.

JAXB Issues

JAXB library missing

Problem
Exception in thread "main" java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException
at somewhere.in.your.Code
Caused by: java.lang.ClassNotFoundException: javax.xml.bind.JAXBException
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:185)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:496)
... 1 more
Solution

This means the code cannot see the JAXB library DU. You will need to set up a library ref from your SLEE component to the supplied library

LibraryID[name=jaxb-runtime-glassfish,vendor=org.glassfish.jaxb,version=2.4.0-b2]

The JAXB API and other runtime dependencies will be visible transitively.

Classloader issues with JAXB unmarshalling

Problem

Classloader is not visible.

Caused by: java.lang.ClassNotFoundException: com.sun.xml.bind.v2.ContextFactory
at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:471)
at com.opencloud.util.MultiparentClassLoader.findClass(MultiparentClassLoader.java:305)
at com.opencloud.util.MultiparentClassLoader.loadClassNoResolve(MultiparentClassLoader.java:187)
at com.opencloud.util.MultiparentClassLoader.loadClass(MultiparentClassLoader.java:205)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:315)
at javax.xml.bind.ServiceLoaderUtil.nullSafeLoadClass(ServiceLoaderUtil.java:120)
at javax.xml.bind.ServiceLoaderUtil.safeLoadClass(ServiceLoaderUtil.java:155)
at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:267)
Solution

Code to create a JAXBContext for deserialisation may need to be created using a Classloader that has visibility of the JAXB library. Use the classloader aware variant of the JAXBContext factory method.

Use JAXBContext.newInstance(String contextPath, Classloader classloader) over JAXBContext.newInstance(Class…​ classesToBeBound)

- c = JAXBContext.newInstance(type);
+ c = JAXBContext.newInstance(type.getPackageName(), type.getClassLoader());

The classloader associated with the class containing the code should work.

Guava Issues

Guava has been upgraded to resolve some issues. There are several API changes, common issues include:

Objects.toStringHelper()
Problem
[oc-javac] /some/where/in/my/Code.java:46: error: cannot find symbol
[oc-javac] return Objects.toStringHelper("ZhRequest")
[oc-javac] ^
[oc-javac] symbol: method toStringHelper(String)
[oc-javac] location: class Objects
Solution

Guava moved this function to MoreObjects, update code to use MoreObjects.toStringHelper()

- import com.google.common.base.Objects;
+ import com.google.common.base.MoreObjects;
@Override
public String toString() {
-  return Objects.toStringHelper("Code")
+  return MoreObjects.toStringHelper("Code")
Iterators.emptyIterator()
Problem
[oc-javac] /somewhere/in/your/Code.java:71: error: <T>emptyIterator() is not public in Iterators; cannot be accessed from outside package
[oc-javac] when(empty.iterator()).thenReturn(Iterators.<CassandraRow>emptyIterator());
[oc-javac] ^
[oc-javac] where T is a type-variable:
[oc-javac] T extends Object declared in method <T>emptyIterator()
Solution

Use Collections.<T>emptyIterator()

MoreExecutors.sameThreadExecutor() has disappeared
Solution

Replace MoreExecutors.sameThreadExecutor() with MoreExecutors.directExecutor()

Misc Issues

Problems with Javadoc validation
Solution

Fix the javadoc. More recent versions of Java are stricter with validation.

Commonly seen tag problems are:

  • @link used when class is inaccessible. Suggest changing to @code as previously it would not have worked.

  • @return used for void methods. Can be solved by removing tag.

  • Mismatched {}. Can be solved by adding missing brackets.

Another common problem is the source set being incorrect meaning javadoc cannot find the required classes. This can usually be fixed by either moving the files to where javadoc is looking, or telling javadoc to look somewhere else.

JDK11 FilePermission change
Problem

At deployment or at runtime,

java.security.AccessControlException: access denied ("java.io.FilePermission" "xxxxx" "read")
Solution

From OpenJDK9: it removes pathname canonicalization from FilePermission creation, thus calculations of the equals() and implies() methods will be based on the raw path string one provides in "new FilePermission(path, action)". details: http://mail.openjdk.java.net/pipermail/jdk9-dev/2016-October/005062.html

By default Rhino startup scripts use -Djdk.io.permissionsUseCanonicalPath=true to work around this behaviour.

JRE removal from JDK11
Problem

JRE is removed from JDK11, paths originally with jre will be broken.

Solution

Some paths wll need updating, for example, path jre/lib/security/cacerts will be lib/security/cacerts

Guice has moved
Problem
[ivy:resolve] ::::::::::::::::::::::::::::::::::::::::::::::
[ivy:resolve] :: UNRESOLVED DEPENDENCIES ::
[ivy:resolve] ::::::::::::::::::::::::::::::::::::::::::::::
[ivy:resolve] :: com.google#guice;4.2.2: not found
[ivy:resolve] ::::::::::::::::::::::::::::::::::::::::::::::
[ivy:resolve]
Solution

Organisation name has changed, need to update ivy dependency

manually:

- <dependency org="com.google" name="guice" rev="${guice.ivy.revision}" conf="self,impl -> war,util" />
+ <dependency org="com.google.inject" name="guice" rev="${guice.ivy.revision}" conf="self,impl -> war,util" />

OR automatically for a whole repo:

find . -name ivy.xml -exec grep 'dependency.*org="com.google".*name="guice"' {} \; \
-exec sed --in-place '/dependency.*name="guice"/s/org="com.google"/org="com.google.inject"/' {} \; \
-exec grep 'dependency.*name="guice"' {} \; -print
Log4j has moved
Problem
[ivy:resolve] ::::::::::::::::::::::::::::::::::::::::::::::
[ivy:resolve] :: UNRESOLVED DEPENDENCIES ::
[ivy:resolve] ::::::::::::::::::::::::::::::::::::::::::::::
[ivy:resolve] :: apache#log4j;2.11.2: not found
[ivy:resolve] ::::::::::::::::::::::::::::::::::::::::::::::
[ivy:resolve]
Solution

manually:

- <dependency org="apache" name="log4j" rev="${log4j.ivy.revision}" conf="self -> api" />
+ <dependency org="org.apache.logging.log4j" name="log4j" rev="${log4j.ivy.revision}" conf="self -> api" />

OR automatically for a whole repo:

find . -name ivy.xml -exec grep 'dependency.*org="apache".*name="log4j"' {} \; \
-exec sed --in-place '/dependency.*name="log4j"/s/org="apache"/org="org.apache.logging.log4j"/' {} \; \
-exec grep 'dependency.*name="log4j"' {} \; -print
SLF4J 1.7.25 works differently with log4j 2.11.2
Problem
[junit] Failed to instantiate SLF4J LoggerFactory
[junit] Caused an ERROR
[junit] Reported exception:
[junit] org/apache/log4j/Level
[junit] java.lang.NoClassDefFoundError: org/apache/log4j/Level
Solution

Change the conf for slf4j from log4j to log4j-bridge thus manually:

- <dependency org="org.slf4j" name="slf4j" rev="${slf4j.ivy.revision}" conf="self -> api,log4j" />
+ <dependency org="org.slf4j" name="slf4j" rev="${slf4j.ivy.revision}" conf="self -> api,log4j-bridge" />

OR automatically for a whole repo:

find . -name ivy.xml -exec grep 'dependency.*org="org.slf4j".*name="slf4j".*conf=".*log4j' {} \; \
-exec sed -E --in-place '/dependency.*name="slf4j"/s/conf="([^"]+)log4j([";,])/conf="\1log4j-bridge\2/' {} \; \
-exec grep 'dependency.*name="slf4j"' {} \; -print
Local Name Service Common Module Removed
Solution

Local-Name-Service was a common module that could be used to specify host names that should resolve to the loopback address in a system property (com.opencloud.localnameservice.names). It was heavily dependent on a package in the JDK called sun.net.spi.nameservice, which was removed in JDK9 and there does not appear to be a library to re-add it. Fortunately there is a new built in system property that can be used to achieve the same effect: jdk.net.hosts.file. The structure of this file is equivalent to that of the /etc/hosts file.

Problems With Jarjar
Solution

Switch from using third-party library to using ant-jarjar common module. See changes to sdk-tools for an example.