Tuesday, October 24, 2017

Today, after many years, I've got something I wanted to blog rather than tweet out in code snippits and screenshots: I'm starting to get my projects working with Java 9.

I hope to cover all of the challenges and solutions I've come up with, but today I'll be focusing on something "simple": getting a straightforward Maven project to export a Java 9 module while still working on Java 8 and lower.

InvokeBinder

Some years ago I started work on a library called InvokeBinder (com.headius:invokebinder), a fluent wrapper around the Java 7 "MethodHandles" API in java.lang.invoke. InvokeBinder provides a more straightforward way to manipulate method handles, juggle arguments, and debug problems.

It's also a nice, simple library to try to get into Java 9.

module-info.java

The main stumbling block for exposing a module is this new file format in module-info.java:

Obviously this is not something we can compile on Java 8, so we will need to use a Java 9-compatible toolchain to build at least this one file.

In my case, I also want the rest of the library to work on Java 8, and since compiling module-info.java requires the target class files to be Java 9 format or higher, I'll need to compile everything except module-info.java separately.

Tool Support for Java 9 is Still Weak

I deferred exploring Java 9 until its release because of weak tool support. Now that I'm forced to deal with Java 9 I'm constantly stymied by weak tool support.

Most IDEs have some support for Java 9's syntactic changes, and of course accessing libraries from a Java 9 install is as easy as it was on previous versions. But the structural changes for Java 9: modules, multi-release jars, linking, and ahead-of-time compilation (which is admittedly experimental) generally do not exist.

How bad is it? Even NetBeans, Oracle's flagship IDE, usually the fastest free way to access the latest and greatest Java features...doesn't support these Java 9 features well at all (NetBeans 9 is still in development)

I generally use IntelliJ IDEA, which has always been ahead of the curve on supporting new Java features, and things are somewhat better here, You can define module definitions, but generally can't split JDK versions for a single source tree, the standard layout for a single module.

(Full disclosure: because I'm just trying to support Java 9 now, and I'm mostly migrating additional projects, I've only just started to figure out what features are supported in which IDs and how well; corrections and updates in comments are welcome.)

Because the IDE space is moving quickly, I won't go into how to get your IDE of choice working nicely with Java 9 structural features. As I cross that bridge, I'll try to post about it.

Maven?

Yes, you guessed it: like a majority of projects in the Java world, InvokeBinder still builds with Maven. For my purposes, it's the simplest way to get an artifact build and deployed, and Maven central is still the canonical place people look for libraries.

Maven is made up of thousands of little plugins and libraries, which makes updating any Maven project for a new JDK version an exercise in pain. When I first looked into Java 9 support some months ago, I basically had to give up; too many plugins I use hadn't updated, and in most cases there was no way to work around the incompatibilities.

Thankfully, most core Maven plugins now appear to work properly with Java 9, though most have not started to expose those structural features I discuss above.

To build invokebinder's module targeting Java 9 and build everything else targeting Java 8 required a newer version of the maven-compiler-plugin and some manual configuration, roughly described on this example page for module-info.java. In my case, I did not need to support Java versions earlier than Java 7, so the split toolkit configuration was unnecessary.

With this change, I was able to get my jar to export a module, and it still works properly when run on Java 7 and 8.

OSGI

Before Java 9 modules, the typical fine-grained runtime dependency system of choice has been OSGI, and InvokeBinder does indeed export an OSGI package.

Unfortunately, the plugin I'm using for doing this (the maven-bundle-plugin from the Felix project) has not been released in some time, and the version of the "bnd" library it uses does not work properly on Java 9.

A bit more manual configuration lets us force the plugin to use a newer version of the library, and everything now works: Java 8 or 9 with OSGI and Java 9 with modules!

Update: Travis CI

After finishing the changes needed above, I realized I needed to get this to build and work on Travis CI, which I use to test InvokeBinder.

What's Next?

I am both excited about Java 9 features and required to make JRuby and my other projects work with them, so this will be one of my primary projects over the next few months. I look forward to hearing how you are supporting Java 9, so please comment with corrections, tips, and updates on anything I've posted here.