EDIT (May 4, 2019): TL;DR → Use Gradle Modules Plugin for that.
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. 👎
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:
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
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
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.
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:
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
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
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.