The release of Java 9 is currently slated for late July 2017, so I’ve decided to dive into the new features of Java 9 and get a preview of how it will work with WebSphere Liberty.
This time around, Oracle is doing all of the JDK development out in the open via the OpenJDK community. The big feature of this Java release is modularity, which comes from JSR 376, the Java Platform Module System (JPMS).
Of course, when a monolithic system is modularized, boundaries must be drawn and enforced. This means that existing applications that have worked in previous Java versions might not work immediately with the new modular Java 9. Fortunately, the OpenJDK team has thought ahead and provided several ways that you can override the modular behavior so that applications can run on Java 9 right away. Over time, your overrides can be slowly removed as you have time to refactor your code in ways that honor the new modular boundaries.
My experiment is organized into two phases:
- Phase 1: Bring up WebSphere Liberty on Java 9 using any overrides necessary, with a focus on making as few code changes as possible.
- Phase 2: Examine how Liberty can exploit the JPMS. Specifically, the feasibility of organizing the OSGi-based Liberty runtime and applications running on Liberty into JPMS modules.
Phase 1: Liberty toleration of Java 9
Getting an existing application server up and running on Java 9 was relatively easy, given the amount of architectural changes to the Java runtime going on behind the scenes. The OpenJDK team has done a great job in this regard. However, there were a small number of issues that we ran into while trying to run some of our test apps on Java 9. Here is how we identified the issues and what we did to handle them.
Static code analysis
Before I attempted to start Liberty with Java 9, I ran
jdeps on all of the JAR files in the Liberty product image and test applications in order to identify dependencies on internalized APIs.
jdeps on a large set of JAR files, use the following command:
ls -1 *.jar | while read LINE; do echo "scanning file: $LINE"; jdeps -jdkinternals $LINE; done > ./jdeps_out.txt
jdeps tool does not analyze soft references to JDK internal APIs (e.g. using
Class::forName to load classes). Fortunately, you can write small programs to compute the set of non-exported packages. Mandy Chung has written a ListJDKInternals tool for doing this. Once you have the list of internal packages, you can
grep your codebase to identify soft references.
If you find that your application depends on internalized APIs, you have two options:
- As a fast workaround, use JVM arguments to bypass the modular boundaries of the JDK. For example, if your application depends on an internalized API called
jdk.internal.Foo, which is a member of the
jdk.barmodule, you can specify:
This forces the
jdk.barmodule to export its
jdk.internalpackage to the unnamed module. We cover how to do this for Liberty in the next section.
- As a more complete long-term solution, modify your code to use a different API. Sometimes
jdepsrecommends a substitute API but in other cases you need to look manually for a substitute API.
After completing the static code analysis, I started the Liberty server and ran a variety of tests. We found a number of issues that, unfortunately, we could not work around. We’ve reported these issues to the OpenJDK team, or identified code changes that we will need to make in Liberty.
Here are the issues that were possible, but non-trivial, to work around:
- Loading class files as resources
Bytecode manipulation frameworks (Serp, ASM, etc) typically need to load
.classfiles at runtime using
ClassLoader::getResourceAsStream. The Java 9 spec cracks down on resource encapsulation by enforcing module boundaries via a new API,
Module::getResourceAsStream. Unfortunately, this API is not an option for most frameworks because it was introduced in Java 9 and most frameworks will need to be compatible with Java 7 and 8. As a result, a counter proposal has been made to allow
ClassLoader::getResourceAsStreamto still load resources from any module if the resource name ends in “.class”. If this proposal gets accepted, we expect that frameworks like Serp and ASM will work with Java 9 out of the box.
- Removed APIs
Based on the results of my
jdepsscan, there were a few cases where Liberty code was dependent on removed APIs and there were no substitute APIs available, such as
sun.management.Agent. For these cases, I had to use JVM arguments to export specific packages to the unnamed module (the module that Liberty currently operates in) with
Reminder: JVM arguments can be set for Liberty at an installation-wide level in
$WLP_INSTALL_DIR/etc/jvm.optionsor at the individual server level in
$WLP_USER_DIR/servers/$SERVER_NAME/jvm.options. For more information, see the IBM documentation.
- Pre-Java 9 library dependencies
In most cases, dependencies that are not updated for Java 9 will still work due to the flexibility of unnamed modules, automatic modules, and the command line flags. However, there are some libraries, such as Liberty’s JSP compiler (
org.eclipse.jdt.core), that are simply not compatible with Java 9 and will require updates before they can be used with Java 9. Fortunately, in this case there is work underway to update the JDT compiler for Java 9. In the meantime there is a workaround where
<jspEngine useJDKCompiler="true"/>can be set in the
server.xmlconfiguration. The main point here is that if the software depends on inactive libraries, be mindful of the fact that, even with the JVM argument workarounds, not everything will work in Java 9 right away.
In my next blog post, I will describe the next phase of this exercise. In Phase 2, I will examine the feasibility of adapting the OSGi-based Liberty runtime so that it can coexist with the JPMS instead of operating entirely in the unnamed module space. We expect that Phase 2 will be more difficult and we are participating in OpenJDK design discussions to ensure that the JPMS will be compatible with existing OSGi systems such as Liberty.