Java Module System — Part I

Jubin
6 min readMay 20, 2019

--

Java 9 as we all know introduced the Java Platform Module System (JSR 376). This addresses some of the issues of the existing Java ecosystem.

The three main issues from a development point of view that the module system in Java 9 tries to address:

  1. Hiding implementation
  2. Effectively removing dependency on legacy code for a library
  3. Sanitizing the Classpath

Advantages of JPMS

  1. Scalable Platform
  2. Security & maintainability
  3. Improved application performance
  4. Easier developer experience

So then how does the module system promise all this? To answer the question we should first understand Java’s module system.

Sins of the CLASSPATH

A traditional java application involves building libraries into jars and placing them on the class-path of the run-time. The run-time resolves the dependencies for the types using the the package name. Once in the class-path, the JVM treats classes in a JAR no differently from separate class files all in the same root directory. At run-time, as far as the JVM is concerned, an application is just a set of classes in a flat list of packages. This leads to a few issues

Encapsulation is all about hiding what you don’t want to show to the recipient of your library. All your dirty secrets! But the moment you drop your jar into a class-path all your secrets are out! Well at-least the public classes that are meant to be internal to the library. Traditionally library developers used to create the package with the name internal as the unwritten law for types not to be used by clients of the library but there was no enforcement of this.

Another problem is that as the JVM sees the package name as just names, we could have two jars with same package name and hence compromise the application integrity.

It could be that the classes that are required for the application to run is missed all together and the run-time throws a NoClassDefFoundError when execution point hits the point where the class is called for.

Jar Hell like Dll hell is a real issue where in large codebases with a lot of dependencies and the developer has to be extremely careful of what is being extended or called.

The codebase of our dear JDK is itself 20 years old and because of backward compatibility there is a large amount of the codebase that is pulled in even for a tiny application. This size also has a hit on the performance of the JVM. Even classes for outdated technology like CORBA is pulled into the runtime of your modern distributed, functional, highly-concurrent wonder code.

Writing Modular Code

At its simplest modular code in java involves:

  • Its name
  • The packages a module exposes
  • The dependencies of a module

There are other nuances to consider but this is where a novice would begin to design his library as modules.

Maven projects (and most other build tools) use modules already to segregate a large codebase into smaller units. This is however only at the codebase level and all the dependencies are still handled by classpaths. With JPMS classpaths become less relevant as the module based jvm parameters like “ — module-path” , “ — module”, etc. have much more control than the traditional classpath

In the above project I have defined four modules for my application

  • com.jtk.nutrition.cli: Module consisting of the main method to run the application in command line
  • com.jtk.nutrition.ui: Module having the UI component for application (and hence has dependency on the javafx modules)
  • com.jtk.nutrition.loader: Module for loading the data for the main program
  • com.jtk.nutrition.sort: Module consisting of various sorting algorithm for the data being loaded

As you can see the dependency is defined such that the cli and ui components depend on the loader and sorting modules; the ui additionally depends on the javafx modules for the front end.

When using maven they are defined as modules, this is as per normal project setup and has nothing to do with JPMS. What makes it different though is the special module-info.java file specified in each of those module source folder as shown below. These files configure the modules for various behaviour

  • nutrition-loader/src/main/java/module-info.java
module com.jtk.nutrition.loader{
requires java.logging;
exports com.jtk.nutrition.loader.model;
exports com.jtk.nutrition.loader.util;
}
  • nutrition-sort/src/main/java/module-info.java
module com.jtk.nutrition.sort{
exports com.jtk.nutrition.sort.util;
}

The requires keyword indicates the module com.jtk.nutrition.loader requires java.logging module as dependency as it might be calling classes from that module.

The exports keyword indicates that this module only exposes those packages with in the modules to the other modules. This means that if there was a package com.jtk.nutrition.loader.internal, the classes even though public cannot be called from other modules. This effectively encapsulates the internal code from the client application which previously wasn’t possible.

  • nutrition-ui/src/main/java/module-info.java
module com.jtk.nutrition.ui {
exports com.jtk.nutrition.ui;
requires java.logging;
requires javafx.controls;
requires javafx.base;
requires javafx.graphics;
requires com.jtk.nutrition.loader;
requires com.jtk.nutrition.sort;
}

Here the requires keyword makes a dependency to the nutrition-loader and nutrition-sort modules defined above. But it can only access those packages that the module has exposed, i.e. com.jtk.nutrition.loader.model, com.jtk.nutrition.loader.util and com.jtk.nutrition.sort.util.

Once the modules are designed we should be able to build by running

mvn clean package

You could also build the application using command line by running the following for each module

javac -d target/classes -p $PATH_TO_FX --module-source-path src $(find . -name ‘*.java’)

— -module-source-path: Tells the compiler where the source files for the modules are.

--module-path (-p): Tells the compiler/runtime where the compiled modules are that need to be considered for compiling/running code your code

But I found this too tedious and depend on maven to do the hard work.

And then run using the below command from the project folder (windows)

java -p nutrition-loader\target\nutrition-loader-1.0-SNAPSHOT.jar;nutrition-ui\target\nutrition-ui-1.0-SNAPSHOT.jar;C:\Users\jubin\apps\Java\javafx-sdk-11.0.2\lib -m com.jtk.nutrition.ui/com.jtk.nutrition.ui.Main

As you can see the run-time is completely devoid of the class-path references and in its place uses the module based VM parameters

  • -p ( —-module-path ) to indicate the jars/classes where the modules are build to; in my case the target folder for the project
  • -m (--module) to indicate the main class that needs to run and its associated module: com.jtk.nutrition.ui/com.jtk.nutrition.ui.Main

Observable modules: These are the set of modules that a particular module depends on either by configuration or by default.

Usually, the below command should be displaying the entire set of observable modules but however it doesn’t seem to work for maven projects.

java --list-modules --module-path target/classes

As an alternative I right-click on intellij project to generate module diagram, which gives me a visual view of the module dependencies (first diagram above).

Additionally, I had to add the following dependency in my pom.xml for the ui module to work.

<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
</dependency>

Summary:

Essentially we solved two problems so far with the module system. We are able to selectively decide what packages are to be exported from a module there by effectively encapsulating the implementation (i.e. even public classes in the jars are not accessible) and configuring the modules such that each module knows exactly what it depends on without having all the jars in the class-path and hoping for the best.

--

--