Building Java 6-8 Libraries for JPMS in Gradle
Find out how to use Gradle to build Java 6-8 libraries that support JPMS (Java Platform Module System) by providing Java 9
If you need introduction to JPMS itself, check out this nice overview.
This post is primarily targeted at Java library maintainers.
Any such maintainer has to make a choice of which JDK to target:
- Targeting the newest JDKs (JDK 11, or just released JDK 12) provides the developers and users with access to new APIs and more functionality.
- However, it prevents the library to be used by all those users who are stuck on older JDKs.
- And those older JDKs are still very popular, taking ~95% share in 2018, and predicted to take ~90% in 2019. Especially the popularity of JDK 8 (> 80% share) makes it a de-facto standard for now.
So the latter is rightly a deciding factor for many library maintainers. For example, vavr 1.0 was intended to target JDK 11, but will ultimately target JDK 8.
Still, it’s advisable to add some support for JPMS in the hope that it will see wide adoption in future (I’d say 5+ years from now).Stephen Colebourne describes three options here:
- Do nothing (not recommended).
- Minimum: add an
Automatic-Module-Nameentry in your
- Optimum: add a
module-info.classtargeting JDK 9+ while providing all the remaining classes targeting JDK 6-8*.
Here, we’ll delve into how to achieve option 3 (the optimum).
* I write about JDK 6-8 (and not e.g. JDK 5-8) because, in JDK 11,
--release option is limited to range 6-11.
Before we delve into “how”, though, let’s skim over “why”.
Why is it worth bothering with JPMS at all? Primarily because JPMS:
- provides strong encapsulation,
- prevents introducing split packages,
- ensures faster class loading.
To sum up, JPMS is really cool (more here), and it’s in our best interest to encourage its adoption!
So I encourage the maintainers of Java 6-8 libraries to make the most of JPMS:
- for themselves, by compiling
module-info.javaagainst the JDK 6-8 classes of its module and against other modules,
- for their users, by providing
module-info.classfor the library to work well on module-path.
There are two places where
module-info.java can be located:
- with all the other classes, in
- in a separate “source set”, e.g. in
I prefer option 1, because it just seems more natural.
There are two places where
module-info.class can end up:
- in the root output directory, with all the other classes,
META-INF/versions/9(Multi-Release JAR, AKA MRJAR)
Having read a post on MRJARs by Cédric Champeau, I’m rather suspicious of MRJARs, and so I prefer option 1.
Note, however, that Gunnar Morling reports having had some issues with option 1. On the other hand, I hope that 1.5 years from the release of JDK 9, all major libraries are already patched to properly handle
Example Libraries per Build Tool
This section contains a few examples of libraries that provide
module-info.class while targeting JDK 6-8.
- Lombok (JDK 6 main + JDK 9
- ThreeTen-extra (JDK 8 main + JDK 9
- Google Gson – not released yet (JDK 6 main + JDK 9
- SLF4J – not released yet (JDK 6 main + JDK 9
Note that Maven Compiler Plugin provides an example of how to provide such support.
I haven’t found any popular libraries that provide such support using Gradle (please comment if you know any). I only know of vavr trying to do this (#2230).
Existing Approaches in Gradle
ModiTect (by Gunnar Morling) and its Gradle plugin (by Serban Iordache) have some really cool features. In essence, ModiTect generates
module-info.class without the use of
javac, based on a special notation or directly from
However, in case of direct generation from
module-info.java, ModiTect effectively duplicates what
javac does while introducing issues of its own (e.g. #90). That’s why I feel it’s not the best tool here.
Badass Jar plugin
Serban Iordache also created a Gradle plugin that lets one “seamlessly create modular jars that target a Java release before 9”.
It looks quite nice, however:
- in order to build the proper JAR and validate
module-info.java, the Gradle build has to be run twice,
- it doesn’t use
--releaseoption, which guarantees that only the right APIs are referenced,
- it doesn’t use
Again, I feel it’s not the right tool here.
This is my most recent find: JpmsGradlePlugin by Axel Howind.
The plugin does some nice things (e.g. excluding
javadoc task), however:
- it too doesn’t use
- it doesn’t support Java modularity fully (e.g. module patching),
- it doesn’t feel mature enough (code hard to follow, non-standard behavior like calling
Proposed Approach in Gradle
Initially, I wanted to do this by adding a custom source set. However, it turned out that such an approach would introduce unnecessary configurations and tasks, while what we really need is only one extra task, “hooked” properly into the build lifecycle.
As a result, I came up with the following:
- Add a new
compileModuleInfoJavaand configure it to:
- include only
- use the classpath of
- use the destination directory of
- depend on
- include only
classestask to depend on
The above, expressed as a Gradle script in Groovy DSL, can be found in this Stack Overflow answer of mine.
* These three steps are necessary for
compileModuleInfoJava to see classes compiled by
javac wouldn’t be able to compile
module-info.java due to unresolved references. Note that in such configuration, every class is compiled just once (unlike with the recommended Maven Compiler Plugin configuration).
Unfortunately, such configuration:
- is not easily reusable across repositories,
- doesn’t support Java modularity fully.
Gradle Modules Plugin
Finally, there’s a plugin (Gradle Modules Plugin) that adds full support for JPMS to Gradle (created by the authors of Java 9 Modularity, Sander Mak and Paul Bekker).
This plugin only lacks support for the scenario described in this post. Therefore, I decided to:
- file a feature request with this plugin: #72
- provide a Pull Request with a complete implementation of #72 (as a “proof of concept”): #73
I tried hard to make these high-quality contributions. The initial feedback was very welcome (even Mark Reinhold liked this!). Thank you!
Now, I’m patiently waiting for further feedback (and potential improvement requests) before the PR can be (hopefully) merged.
In this post, I’ve shown how to build Java 6-8 libraries with Gradle so that
module-info.java is compiled to JDK 9 format (JPMS support), while all the other classes are compiled to JDK 6-8 format.
I’ve also recommended to use Gradle Modules Plugin for such configuration (as soon as my PR gets merged and a new plugin version gets released).
Published on Java Code Geeks with permission by Tomasz Linkowski, partner at our JCG program. See the original article here: Building Java 6-8 Libraries for JPMS in Gradle
Opinions expressed by Java Code Geeks contributors are their own.
Gradle Modules Plugin v1.5.0, which includes my PR, has been released! 😃