Preamble: this blog has also been posted on the technical blog of my company.
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.
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 –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 follwing illustrates to list the first ten of them:
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:
- The module name (
modulekeyword): usually the name of the main package (following the reverse Internet domain-name convention),
- The list of exported packages (
exportskeyword): 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,
- The list of imported packages (
requireskeyword): 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:
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:
- 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,
- There is no version management in JPMS. As a reminder, OSGi defines a version attribute for its
Export-Packagedirectives. Version management has been considered too complex for JPMS in a first step and postponed to a future release(to be confirmed). 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-Namevariable in MANIFEST.MF. See Automatic-Module-Name 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:
Step by step migration
For a given module candidate (a traditional jar file), 3 migration steps can be identified:
- As is migration on the Java 9 runtime with minimum change for technical compatibility; in the classpath and hence part of the unnamed module,
- Pre-modularization: by assigning the final module name using the
Automatic-Module-Namevariable (to be setup in
META-INF/MANIFEST.MF); in the modulepath as an automatic module,
- 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.
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.
- 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.
- 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.
- Shows how to package a module in a jar file,
- Warning: the jar option
print-module-descriptorhas been replaced by
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.
- Show how to build a custom Java image from modules using
- This feature deserves a specific paragraph (see below).
Zoom on jlink
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
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:
To run the image, use the command:
java -m com.greetings/com.greetings.Main
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: