Building Java 8-compatible Modular JARs

Aleks Seovic
3 min readFeb 8, 2018

--

Java 9 modules are fairly straight forward to build and use if you are building Java 9-only applications. Tool support, and by that I mean both Maven and IntelliJ, as that’s what we use, is pretty good already and is getting better.

However, sometimes you need to build JAR files that can be consumed from both Java 8 (or older) and Java 9 applications, and in the latter case you want them to be proper Java 9 modules, with all the goodness of module_info, encapsulation, etc, and not just automatic module names in the manifest.

We have the need for these types of modular Java 8 JARs because we are building a modular Java 9 server that expects proper Java 9 modules for things that are deployed into it, but we also want to make some of the low-level libraries we use within it accessible to people still using Java 8.

I was originally hoping javac would be smart enough to realize what to do if I specify -release 8 (it should be able to guess that I’m not targeting module_info.javafor Java 8 and to compile it using Java 9 compiler instead). Unfortunetely, it doesn’t, although there is supposedly an open issue to allow it to do so, so in the meantime we have to hack it.

Existing Solutions

Nicolai Parlog published a good article on the subject, but unfortunately both proposed approaches have a downside of having to setup a multi-module project in order to get them to work with Maven or any IDE.

Multi-Release JARs in particular are a real pain, don’t play well with either IDEs or Maven yet, and seem to be quite an overkill for something this simple.

Our Solution

The solution we ended up with is based on the double-compilation approach described here, with some minor modifications to make Java 9 the default and Java 8 compilation just an additional, Maven-only, compilation step:

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<executions>
<execution>
<id>default-compile</id>
</execution>
<execution>
<id>java8-compile</id>
<goals>
<goal>compile</goal>
</goals>
<!-- recompile everything with Java 8 except the module-info.java -->
<configuration>
<release>8</release>
<excludes>
<exclude>module-info.java</exclude>
</excludes>
</configuration>
</execution>
</executions>
<configuration>
<release>9</release>
<!-- still need this for IntelliJ -->
<source>9</source>
<target>9</target>
</configuration>
<dependencies>
<!-- upgrade to final ASM 6.0 release -->
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>6.0</version>
</dependency>
</dependencies>
</plugin>

Note: The ASM 6.0 dependency is unrelated, but required in order for test compilation to succeed with Java 9. By default, Maven Compiler Plugin uses 6.0_ALPHA which is completely broken and fails to parse module_info.class, as the module descriptor format has changed since it was released.

The above compiles everything first with Java 9, which ensures that everything we have in module_info is valid, and then overwrites all but module_info.class with the Java 8 compiled classes. Then everything gets neatly packaged into a JAR, as usual, using maven-jar-plugin.

When you use generated JAR with Java 9, module descriptor will be taken into account and it will behave just like any other Java 9 module. When you use it with Java 8 (or whichever older version of Java you chose to compile to), module descriptor will simply be ignored.

The solution above allowed us to avoid multi-module projects (and God forbid, MR JARs) for a task as simple as including Java 9 module descriptor into a Java 8 JAR.

As far as IntelliJ is concerned, we are working on a regular Java 9 module, so it validates module_info.java for us and does all the other nice Java 9-specific things. The downside, of course, is that it will not warn us if we use any Java 9 APIs or features in the code, but we figured that we’ll find about any such violations quickly enough and certainly before we commit any code to our Git repo, when we run a full Maven build.

The upside is that we can still use Java 9 features in tests, which allow us to use some of the syntactic sugar, such as Set.of, List.of, etc. This helped clean up our tests quite a bit in some places.

Conclusion

Hopefully we are not the only ones trying to build Java 9 modules that are still usable in older versions of Java, so this may come in handy.

If you know of a better, simpler way to achieve the same end result, please let me know in the comments section below.

--

--

Aleks Seovic

Father of three, husband; Coherence Architect @ Oracle; decent tennis player, average golfer; sailor at heart, trapped in a power boat