Upgrading to Java 9

I've recently helped upgrade Apache Camel to Java 9 following the Quality Outreach initiative. This post is meant to help out anyone trying to do the same. Note that at the time of writing this, Java 9 hasn't been released yet so things might change but I'll try to keep this up to date.

There are some interesting talks about Java 9 which I highly recommend. It will save you a lot of time in the long run. This Java upgrade introduces a lot of changes in particular the modularization of the JDK with Project Jigsaw. I'll also recommend reading JEP 223, JEP 260 and JEP 261.

Now that you've done you're reading ;) we can start upgrading. First let's define the scope of the upgrade. As of now, the tooling support for building module-info.java files isn't there yet (although some projects are starting to surface). Therefore our goal here will be to build and run our tests with Java 9 using the new command-line flags to break encapsulation and add modules to the module path.

Before even trying to build we need to upgrade a few things.
  • Make sure you're using Maven 3.0 at the very least.
  • Upgrade some known maven plugins for Java 9 compatibility. The most common ones being the compiler(3.6.1), javadoc(2.10.4), surefire(2.19.1), war(3.0.0) and jar(3.0.2) plugins. 
  • Remove dependency on lib/tools.jar as it's no longer present. This can be done by moving this to a Maven profile with activation:
  • This will be used a lot. See the Version Range Specification for more details.
  • Use the Maven JDeps Plugin to detect usages of internal API calls and fix them if you can.

Ok now you're ready to download the latest version of Java 9 and try out your build. Fingers crossed :) By the way when working with multiple versions of Java, I've found using Jenv with the Maven extension to be very useful to easily switch between Java versions. This last section will cover the different types of errors you can encounter and how to fix them.

Troubleshooting

JEP 223

JEP 223 introduces a new versioning scheme which brakes the way we used to get the java version at runtime:

This error was seen in the plexus archiver (fixed in 3.0.3) which a lot of plugins depend upon. This was because the java version was retrieved in the following way (most java code does the same):

Which poses a problem given the new versioning scheme. Here's a Java 9 compliant way of doing it:

This best way to tackle this is to search all occurences of 'java.version' in your code ahead of time and not wait to run into the issue, this will save you some build time.

JEP 261

JEP 261 introduces the module system. I'll assume you've understood the basic principles and we'll focus on troubleshooting. Here's the one you'll probably encounter first:

Following our new module system, you'll have to add the java.xml.bind module using the --add-modules flag. If you get a different missing class, check out the Java 9 overview and search you're missing class to find the module name. The logic remains the same. There are a couple ways to fix this and it's important to know the differences. First off, if this happens during build time then you can configure the maven-compiler-plugin as so:

Notice the fork option is set to true and the -J to pass the flag directly to the runtime system. If this happens during tests you can configure the maven-surefire-plugin:

The above way of doing this is nice because these maven plugins support forking the JVM. That way you don't have to pollute the MAVEN_OPTS environment variable and users can just run the build with 'maven clean install'. However a lot of maven plugins don't support forking. This is the case for the maven-jaxb2-plugin for example. As an ultimate last resort you can also add the corresponding maven dependency to bypass the ClassNotFoundException. In our case adding the dependency:

will solve our problem. I only recommend this as a last resort.

A very common thing we do in Java is reflection and proxying. Almost every framework out there does it. This will pose some problems in Java 9. For example, when using Spring proxies, one might get the following error:


This error is because Spring is trying to modify the access level of a class inside the java.lang package in the java.base module. Adding --add-opens java.base/java.lang=ALL-UNNAMED to your command-line will solve this issue. Same as for the --add-modules flag, the --add-opens flag (and this will be valid for all flags) can be added to the surefire or compiler plugin (or even the MAVEN_OPTS environment variable as a last resort).

Last but not least, you may be trying to access a now private enforced package, in which case you'll get something like this:


Adding --add-exports=java.xml.bind/com.sun.xml.internal.bind.v2.runtime=ALL-UNNAMED to your command-line will solve this issue.

I hope this post has helped you upgrade to Java 9! Enjoy ;)


Comments

Popular Posts