5 Gradle plugins for working with modular Java projects

Java 9 was release in September 2017 and with it came a brand new modular platform known as JPMS, delivered by Project Jigsaw. Building projects that an leverage this new feature can be a bit daunting however the Gradle plugins in the following list can help you in keeping things under control.

1. JDeps - https://github.com/aalmiray/jdeps-gradle-plugin

This plugin generates a report of your production code and compile/runtime dependencies using the jdeps tool available since Java 8. Jdeps can tell you if your codebase is vulnerable to API changes such as relying on internal APIs (like sun.misc.Unsafe) even if you don't see those usages directly in your code. Here's a sample report of a project that makes use of Guava 26.0-jre as a dependency

Dependency: guava-26.0-jre.jar
guava-26.0-jre.jar -> /Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/rt.jar
   com.google.common.cache.Striped64 (guava-26.0-jre.jar)
      -> sun.misc.Unsafe                                    JDK internal API (rt.jar)
   com.google.common.cache.Striped64$1 (guava-26.0-jre.jar)
      -> sun.misc.Unsafe                                    JDK internal API (rt.jar)
   com.google.common.cache.Striped64$Cell (guava-26.0-jre.jar)
      -> sun.misc.Unsafe                                    JDK internal API (rt.jar)
   com.google.common.hash.LittleEndianByteArray$UnsafeByteArray (guava-26.0-jre.jar)
      -> sun.misc.Unsafe                                    JDK internal API (rt.jar)
   com.google.common.hash.LittleEndianByteArray$UnsafeByteArray$1 (guava-26.0-jre.jar)
      -> sun.misc.Unsafe                                    JDK internal API (rt.jar)
   com.google.common.hash.LittleEndianByteArray$UnsafeByteArray$2 (guava-26.0-jre.jar)
      -> sun.misc.Unsafe                                    JDK internal API (rt.jar)
   com.google.common.hash.LittleEndianByteArray$UnsafeByteArray$3 (guava-26.0-jre.jar)
      -> sun.misc.Unsafe                                    JDK internal API (rt.jar)
   com.google.common.hash.Striped64 (guava-26.0-jre.jar)
      -> sun.misc.Unsafe                                    JDK internal API (rt.jar)
   com.google.common.hash.Striped64$1 (guava-26.0-jre.jar)
      -> sun.misc.Unsafe                                    JDK internal API (rt.jar)
   com.google.common.hash.Striped64$Cell (guava-26.0-jre.jar)
      -> sun.misc.Unsafe                                    JDK internal API (rt.jar)
   com.google.common.primitives.UnsignedBytes$LexicographicalComparatorHolder$UnsafeComparator (guava-26.0-jre.jar)
      -> sun.misc.Unsafe                                    JDK internal API (rt.jar)
   com.google.common.primitives.UnsignedBytes$LexicographicalComparatorHolder$UnsafeComparator$1 (guava-26.0-jre.jar)
      -> sun.misc.Unsafe                                    JDK internal API (rt.jar)
   com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper (guava-26.0-jre.jar)
      -> sun.misc.Unsafe                                    JDK internal API (rt.jar)
   com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper$1 (guava-26.0-jre.jar)
      -> sun.misc.Unsafe                                    JDK internal API (rt.jar)

The production code itself is safe but dependencies are not. This particular project may encounter problems when moving to a version of Java greater than 8. This report lets you know that it may be a good idea to review usages of Guava and perhaps consider alternatives if the project ought to be run using a much newer version of Java. Builds that use this plugin may run using Java 8.

2. JDeprScan - https://github.com/aalmiray/jdeprscan-gradle-plugin

The jdeprscan tool is also available in OpenJDK distributions since Java 9. This tool generates a report of APIs that have been marked for deprecation and that are still referenced in your code. Typically you'll run the build with a newer version of Java (at least Java9) however the plugins runs also on Java 8 but you'll have to configure an external reference to a JDK that provides the jdeprscan tool.

3. Java9c - https://github.com/jonnyzzz/gradle-java9c

This plugin scans your code for possible problems with split packages. One of the constraints brought by Project Jigsaw is that it's no longer possible to have a package splitted in different modules and/or JAR files. Here's a typical scenario, say you have the following dependencies in your build

dependencies {
    testCompile 'junit:junit:4.12'
    testCompile 'org.hamcrest:java-hamcrest:2.0.0.0'
}

Running the java9c task on the build results in the following error being printed to the console and a failed build

The following packages are defined in several modules:
  
Package 'org.hamcrest' is declared in
  - hamcrest-core.jar (org.hamcrest:hamcrest-core:1.3)
  - java-hamcrest.jar (org.hamcrest:java-hamcrest:2.0.0.0)
  
  
Package 'org.hamcrest.core' is declared in
  - hamcrest-core.jar (org.hamcrest:hamcrest-core:1.3)
  - java-hamcrest.jar (org.hamcrest:java-hamcrest:2.0.0.0)
  
  
Package 'org.hamcrest.internal' is declared in
  - hamcrest-core.jar (org.hamcrest:hamcrest-core:1.3)
  - java-hamcrest.jar (org.hamcrest:java-hamcrest:2.0.0.0)

This problem is caused because there are 3 packages provided by two different JAR files. We can fix the problem by adding an exclusion such as this one

dependencies {
    testCompile('junit:junit:4.12') {
        exclude group: 'org.hamcrest', module: 'hamcrest-core'
    }
    testCompile 'org.hamcrest:java-hamcrest:2.0.0.0'
}


This plugin also runs with Java 8 as a minimum.

4. Modules - https://github.com/java9-modularity/gradle-modules-plugin

So far we've seen plugins that can help you prepare your codebase for getting into modules. This plugin is the one you need to build, test and run modular projects. The easiest way to make this work is by placing a proper module descriptor (a file named module-info.java at the top of the source tree) and let the plugin do its thing. This link points to a trivial JavaFX 11 project that makes use of all these plugins so far. Here's how the module descriptor for this project looks like

module hellofx {
    exports hellofx;
    requires javafx.base;
    requires javafx.graphics;
    requires javafx.controls;
}

This is enough information for the modules plugin to configure the module path for compiling, testing, and running the application.

5. JLink - https://github.com/beryx/badass-jlink-plugin

Finally, one of the reasons to make use of modules is the ability to create a custom JVM image that contains the smallest possible subset of features required for an application to run. You may need to add a few extra bits of configuration when applying this plugin. Here's how the trivial project linked earlier makes use of this plugin

jlink {
    options = ['--strip-debug', '--compress', '2', '--no-header-files', '--no-man-pages']
    launcher {
        name = 'hellofx'
    }
}

When running the jlink task we get a custom JVM image located at build/image. If you inspect the size of this folder you may see it weights about 45M, which is less than half of the typical JVM. This image also includes launchers for the application, named hellofx and hellofx.bat. Now all you need to do is make use of the launcher suitable for your platform and the application runs without a hitch! The plugin also includes an option to zip the generated image, making it a little easier to distribute your custom image + production code to whoever needs it.

Keep on coding!

Liked it? Take a second to support aalmiray on Patreon!
Become a patron at Patreon!

1 comment

  • I wish there were a tool that, given a (long) list of dependencies, translated them into the Java 9 module names. Without that, I am very sad!

Trackbacks/Pingbacks

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

ˆ Back To Top