Java 9: discovering modules

9 minute(s) read

After some tough negotiations within the JCP (Java Community Process), OpenJDK 9, the Reference Implementation of Java Standard Edition, is to be released on September 21 2017. It will bring about 80 new features, the most important one being Java Platform Module System (JPMS), more known by its project name Jigsaw. For the rest of this article, I will use both terms (JPMS and Jigsaw) without differentiation.

Ready for Java 9?

Executive Summary

JPMS brings native modularization to the Java platform. It is also intended to be used by all Java developers.

Moving from monolithic to complete modularization will take a while. Intermediate steps will be necessary to introduce it progressively. Many of the recent discussions inside the JCP have focused on how to facilitate this migration.

Understanding the basics of JPMS and why some of its strict rules have been temporarilly relaxed is of high importance to define a proper migration strategy.

What are the expected benefits of Java modularity?

  • Ease of maintenance thanks to a strict encapsulation mechanism enabling to differentiate external APIs (those that are intended to be exposed outside) from internal ones (those that are related to implementation details),
  • Improved security by reducing the runtime surface (number of modules) and by controlling the external APIs,
  • Increased scalability to address embedded systems and microservices. As we will see, custom Java images can be built, containing only the needed modules to run the application ,
  • Better performance thanks to a reduced system footprint (plus other internal optimizations independant from Jigsaw such as Compact Strings),
  • Paves the way for a faster delivery of new Java features. For instance, the new HTTP/2 Client is delivered as an incubator module.

New structure of the JDK

At first glance, we can see that the file structure of the JDK has been simplified:

Java 9 layout

In particular:

  • there is no more jre (Java Runtime Environment) subdirectory with partial duplication of files in bin and lib,
  • rt.jar and tools.jar files have disappeared,
  • same for lib/ext and lib/endorsed directories.

The java –list-modules command identifies 75 modules. Some modules are standard ones (they include their own classes) while others are aggregators (they collect and export other modules, such as java.se.ee and java.se).

java.base is imported by default. Other modules must be defined in the new modulepath and imported by consumer modules.

As an example, the following illustrates how to list the first ten of them:

Java 9 list modules

Modules are also visible in the form of *.jmod files in the jmods directory. However these files are not intended for runtime, they’re dedicated to compilation and jlink to build custom Java images.

What about Java developments?

JPMS has been designed for the Java platform itself. It is also open (and recommended) for all developments: tools, frameworks, business components. The impact will not be neutral and it will take a while before seeing whole applications properly modularized. To facilitate things in the short term, it has been decided to relax “strict encapsulation” (the way modules know each other) as we will see below.

Anatomy of a module

A module is characterized by a meta-info file descriptor. This is a Java file containing meta-data, the most important ones being:

  1. The module name (module keyword): usually the name of the main package (following the reverse Internet domain-name convention),
  2. The list of exported packages (exports keyword): those which are visible outside the module. Warning: this means that a public class may not be accessible to all classes running in the same JVM! It remains public in its module, but must be explicitly exported to be used out of its border,
  3. The list of imported packages (requires keyword): those which are necessary to run the current module.

For instance, here is an example of two related module-info from the Quick Start described below:

module-info examples

A module is usually packaged as a jar file with a meta-info descriptor at its root file hierarchy.

Compared to OSGi, the historical modularization system of the Java platform, we can observe two fundamental differences:

  1. JPMS is natively taken into account by the Java language and platform which enables to detect errors natively, for instance at compile time. As a reminder, OSGi is not natively managed by Java. Its meta-data are text-based and declared in META-INF/MANIFEST.MF. This can lead to dependency errors at runtime,
  2. There is no version management in JPMS. As a reminder, OSGi defines a version attribute for its Import-Package and Export-Package directives. Version management has been considered too complex for JPMS in a first step and postponed to a future release. In the meantime, build tools (Maven, Gradle …) will have to deal with that issue. Warning: two distinct versions of the same module (same module name) will be seen as a duplication which is forbidden.

Is that good old classpath still alive?

We have introduced the new concept of modulepath, but what happens to the old classpath with Java 9? Rest assured, it is still there, at least for compatibility reasons! All classes belonging to the classpath are parts of a so-called unnamed module, which by default exports nothing and requires all modules.

Warning: this strict encapsulation rule means that a class in the classpath cannot be imported from the module path. This can be really painful during the migration phase with classes spread across the classpath and modulepath. To make things easier, it has been decided, on the very last mile of the specification, to break this rule in the first releases of OpenJDK 9 (with --illegal-access option set to permit by default). Thus, the whole classpath will be implicitly accessible from the modulepath.

What if I put my jar in the modulepath?

Putting a traditional jar file (without module-info) in the modulepath is allowed. This leads to a so-called automatic module with the following rules:

  • The module name is computed from the jar file name. This mechanism has been highly criticized during the JCP negotiations for consistency and evolution reasons. The generated module name may not be the one chosen for proper modularization. Hence creating dependencies on such a temporary name can lead to troubles. Explicit naming is possible and recommended by setting the Automatic-Module-Name variable in MANIFEST.MF. See [Automatic-Module-Name usage] (http://openjdk.java.net/projects/jigsaw/spec/minutes/2017-05-18#AutomaticModuleNames–ModuleNameInManifest) for more details,
  • Everything declared as public is exported,
  • All other modules are required.

As a good practice, it is recommended to explicitly name modules using the Automatic-Module-Name variable as described above. It enables to prepare the full modularization by assigning a future-proof module name.

Here is a summary of the module types:

Java 9 module types

  • (1) Relaxed mode will be the default for OpenJDK9 first releases
  • (2) Strict encapsulation remains the target for future OpenJDK9
  • (3) Setting Automatic-Module-Name is highly recommended

Step by step migration

For a given module candidate (a traditional jar file), 3 migration steps can be identified:

  1. As is migration on the Java 9 runtime with minimum change for technical compatibility; in the classpath and hence part of the unnamed module,
  2. Pre-modularization: by assigning the final module name using the Automatic-Module-Name variable (to be setup in META-INF/MANIFEST.MF); in the modulepath as an automatic module,
  3. Full modularization: complete refactoring to differentiate exported APIs based on a module-info descriptor; in the modulepath as an explicit module.

In reality, for a given application, these steps will have to be managed for plenty of jars (tools, libraries, frameworks, business and so forth), each one at its own pace of migration. The migration complexity will come from the diversity of situations.

Running the Module system Quick-Start guide

After getting some insights into JPMS, you are now ready for the official Quick-Start It provides with very simple examples enabling to grab the big picture and foresee the real challenges. Although it is not brand new, it remains relevant. I don’t intend to paraphrase it, but rather to explain how I have proceeded and share my impressions.

To isolate things, I’ve used an official Docker image provided by the OpenJDK community: opendk/9-b177-jdk:

docker run -it --rm -v $HOME/jdk9dev:/jdk9dev --workdir /jdk9dev openjdk:9-b177-jdk

Where $HOME/jdk9dev is my development directory. Four your information, Java is installed in /usr/lib/jvm/java-9-openjdk-amd64.

The java –version command enables to check that we have entered the era of modularity. Warning: this is a minimalistic image with very few tools. It’s better to mount a volume and work from a complete Linux distribution to process files. Let’s review the examples provided by the Quick Start.

Greetings:

  • A first very basic example describing a module without dependencies (other than java.base),
  • Enables to grasp the module-info file and the java compilation directives,
  • No specific packaging: classes are left in a mods subdirectory.

Greetings World:

  • Things get a bit more complicated with the arrival of a second module and dependency rules set up,
  • Shows how to manage dependency between 2 modules and how to compile them individually.

Multi module compilation:

  • Shows how to compile two modules at the same time with a single javac command.

Packaging:

  • Shows how to package a module in a jar file,
  • Warning: the jar option print-module-descriptor has been replaced by describe-module,

Missing requires or missing exports:

  • Show how errors are detected at compile-time in case of module-info inconsistency,
  • A little overview of what lies ahead of us.

The linker:

  • Show how to build a custom Java image from modules using jlink,
  • This feature deserves a specific paragraph (see below).

jlink is a brand new feature of JPMS and an illustration of the concept of just enough runtime. It targets embedded devices and may also be very interesting for microservices. Here is the command to run:

jlink --module-path $JAVA_HOME/jmods:mlib --add-modules com.greetings --output greetingsapp

Where:

  • module-path: where to find the modules (both Java Platform and others),
  • add-modules: which top modules to take into account,
  • output: where to generate the image.

And here is the magic: jlink takes into account transitive dependencies from the top modules and generates a custom Java image. What’s inside it? In fact, it is a reduced Java distribution (same file hierarchy as a standard Java distribution). The java –list-modules command enables to list its modules:

list modules of a custom Java image

To run the image, use the command:

java -m com.greetings/com.greetings.Main

Conclusion

In this post, we’ve browsed:

  • The main principles of JPMS:
  • The basic structure of a module,
  • The different kinds of modules and their behaviors: unnamed, automatic, explicit,
  • How the classpath and the modulepath interact,
  • How to run the Quick-Start to get a concrete view,
  • How to proceed with migration.

I hope that this has made you more comfortable with the upcoming Java modularity and that you’ve realized that the migration effort must not be underestimated.

For a full Java 9 migration, keep in mind that there are many other features to take into account: multi-release jar, unified logging, command options change, G1 as default Garbage Collector, reactive streams, HTTP/2 support … Oracle has published an excellent Java magazine issue this summer about them. An official migration guide will provide you with all relevant information.

Do not desperate:

Written by

Jean-François James

Still passionate after more than 30 years of experience in IT! DevOps advocate, head of the Expert Network at Worldline France