Back to /

Working with Java 9 modules

Modules system was added to Java in version 9, but I've never used it before. I think I never need it. I'm using Maven for my work and home Java projects and I never thought modules will bring something worth the effort of learning of new technology. Recently I was doing small Java application for my friends. It was good opportunity for learning modules for fun and profit.

Building modules

One of the features of Java modules is strong encapsulation. It means that example package com.acme.example from Module A is accessbile in Module B when all following conditions are met:

Each module declares exported and required packages in special file module-info.java, that is saved in top-level directory. In my Maven project it was src/main/java.

My project is a Java Swing application, that depends on Apache POI library. In that case module-info.java looks like this

module com.milosz.mergeupp {
  requires java.desktop;
  requires java.logging;
  requires org.apache.poi.poi;
}

I declared, that my application requires java.desktop and java.logging modules from Java SDK. Without that statement I couldn't use Swing classes or Java Logging config. Modern IDE are supporting moduels and immediately inform about missing dependencies. Last line org.apache.poi.poi declares dependency on Apache POI package. java.base module is provided by default and don't have to be included in require list. Unlike in Maven if I declare dependency on Module A doesn't mean, that I also have access to transitive dependencies of that module. For example I won't be able use classes from library commons-collections4, that is dependency of Apache POI until I add org.apache.commons.collections4 to requirements list in module-info.java.

In order to build modularized application with Maven, ensure that you're using maven-compiler-plugin at least in version 3.6.0.

Custom runtime

Application is quite small and takes about 18 MB with all dependencies. At the same time user needs to download ~200 MB package with Java runtime. Of course same runtime can be reused by all Java applications, but still 200 MB doesn't look good, when compared application size.

Since I've already implemented modules I've decided to try jlink - Java linker, that was developed in Project Jigsaw.

jlink can assemble and optimize a set of modules and their dependencies into a custom run-time image. If I used it for com.milosz.mergeupp modules, runtime would include only those modules, that are required for running application. Unfortunatelly it didn't work as I expected.

Maven maven-jlink-plugin supports building custom runtime for module project. It has failed on first attempt, because jlink doesn't support so called automatic modules. It means, that modules without explicit modules-info.java cannot be used with jlink. In my case that was all apache-commons packages, which are dependencies of Apache POI. I've workaround that limitation with moditect-maven-plugin. This Maven plugin is able add custom module-info.java to any project dependency.

Example configuration for commons.collections4 module might look like this

<plugin>
  <groupId>org.moditect</groupId>
  <artifactId>moditect-maven-plugin</artifactId>
  <version>1.0.0.RC2</version>
  <executions>
    <execution>
      <phase>generate-resources</phase>
      <goals>
        <goal>add-module-info</goal>
      </goals>
      <configuration>
        <outputDirectory>${project.build.directory}/modules</outputDirectory>
        <modules>
          <module>
            <artifact>
              <groupId>org.apache.commons</groupId>
              <artifactId>commons-collections4</artifactId>
              <version>4.4</version>
            </artifact>
            <moduleInfoSource>
               <![CDATA[module commons.collections4 {
               exports org.apache.commons.collections4.multimap;
             } ]]>
            </moduleInfoSource>
        </module>
      </configuration>
    </execution>
   </executions>
</plugin>

Because POI library requires access to org.apache.commons.collections4.multimap module, it must be exported in dependency. Overriden pacakges land in /target/modules directory and must be explicitly added in jlink plugin configuration.

After I did it for all automatic modules I could build custom runtime. However package took over 200 MB.

Conclusion

I really like Java modules concept and features. Especially explicit list of modules, that can be used in code. I've never liked ability to use transitive dependencies in Maven projects. Requirements list in modules-info ensures, that we don't couple our application with random library.

However I don't think I will use jlink again. It basically required too much configuration. And output wasn't encouraging. Huge runtime, that can be run only on host system (you can't build Windows runtime on Linux). Missing modules-info in popular Apache Commons libraries was also unpleasant surprise.