Java 9 Jigsaw Project Tutorial
In this tutorial we will get a brief introduction to the Java 9 Jigsaw feature by a way of a simple project. This project will demonstrate some of the features of the new module system (Jigsaw) being introduced in Java 9 and how to go about structuring projects in a way that will leverage the features and adhere to the requirements of Jigsaw.
We will cover the mechanics of how the module system will work for new projects and how existing projects and libraries can be retro-fitted (if needed) to leverage the new module system.
We will also demonstrate structuring, building and packaging our simple project both from command line and via the popular build and dependency management tool, Maven in order to leverage the new module system, Jigsaw.
Table Of Contents
1. Introduction
Project Jigsaw is a modularization of the JDK and introduction of a module system for Java bringing about stronger encapsulation, smaller package footprint and reliable configuration to Java applications.
2. Technologies used
The example code in this article was built and run using:
- Java 9
- Maven 3.3.9
- Eclipse Oxygen (4.7.0)
- Ubuntu 16.04 (Windows, Mac or Linux will do fine)
3. Setup
To follow along in this tutorial we only need Java 9 and maven 3.3.9 installed with both bin
folders available on the path. Also your JAVA_HOME
variable needs to be set to the Java 9 install. To verify:
JAVA_HOME
: issueecho $JAVA_HOME
and you should see the path to your Java 9 install shown on screenjdeps
: issuejdeps --version
jlink
: issuejlink --version
jar
: issuejar --version
javac
: issuejavac --version
java
: issuejava --version
mvn
: issuemvn --version
Issuing these commands should give output very similar to below:
Output from Setup commands
export JAVA_HOME=/home/jean-jay/runtimes/jdk-9 echo $JAVA_HOME /home/jean-jay/runtimes/jdk-9 jdeps --version 9 jlink --version 9 jar --version jar 9 javac --version javac 9 java --version java 9 Java(TM) SE Runtime Environment (build 9+180) Java HotSpot(TM) 64-Bit Server VM (build 9+180, mixed mode) mvn --version Apache Maven 3.3.9 Maven home: /usr/share/maven Java version: 9, vendor: Oracle Corporation Java home: /home/jean-jay/runtimes/jdk-9 Default locale: en_ZA, platform encoding: UTF-8 OS name: "linux", version: "4.10.0-33-generic", arch: "amd64", family: "unix"
Add a toolchains.xml
file in your .m2
folder for maven. (replace the jdkHome
locations with your local path to Java 9 install)
ToolChains configuration for Maven
<toolchains> <toolchain> <type>jdk</type> <provides> <version>1.9</version> <vendor>oracle</vendor> </provides> <configuration> <jdkHome>/home/jean-jay/runtimes/jdk-9</jdkHome> </configuration> </toolchain> <toolchain> <type>jdk</type> <provides> <version>1.8</version> <vendor>oracle</vendor> </provides> <configuration> <jdkHome>/home/jean-jay/runtimes/jdk1.8.0_101</jdkHome> </configuration> </toolchain> </toolchains>
4. Goals of Jigsaw
The goals of Jigsaw are:
- Provide stronger encapsulation of components that extend beyond what the JDK currently provides.
- Reliable configuration of modules in an application.
- Reduce the footprint of the run-time image for an application to what is only needed.
4.1 Stronger Encapsulation
As a recap the JDK provides access modifiers that help us promote encapsulation and information / behavior hiding between classes and members of classes on the class-path. These are (in order of most visible to least visible):
- public
- protected
- package private
- private
Now this is quite ample from a jar
file’s perspective (ie within a jar
) but the moment we go beyond the jar
(i.e. collaboration between jars on the class-path) we encounter a limitation. What was once public to the jar
is now actually public to the entire class-path and this might not be what we want. Conveniently classes that are public
within a jar
(domain) but were not intended for use outside that jar
(domain) are now free to be used / misused by consumers of said jar
.
4.2 Reliable Configuration
What was previously not possible is now possible via Jigsaw. Our Java applications will be able to verify dependencies at run-time and enforce the integrity of it.
Although versioning ambiguity remains an open issue (two identical jars with different versions), Jigsaw goes a long way in enforcing dependency integrity by guaranteeing a dependency is available and that their exists no cyclic dependencies between jars / modules.
4.3 Reduction of Package Footprint
By shrink wrapping the JDK and the application code base (including dependencies) into a collection of what is only needed, we can ship much smaller packages when deploying our applications. This is particularly useful when it comes to devices / platforms with resource constraints.
5. Modules
A module is a jar
file which declares it’s dependencies and “public” API via a module descriptor file named module-info.java
. The module-info.java
file specifies the following:
- The name of the module.
- The packages it exports.
- Other modules it depends on.
Modules can be 1 of 4 types:
5.1 Automatic Modules
Backward compatibility has never been an afterthought when it comes to Java and with Jigsaw that is no different. As we stated earlier, a module is a jar
file with a module descriptor in it specifying it’s public contract, however most, if not all 3rd party libraries in use today, do not have a module descriptor and thus are not modules.
A method to allow us to use 3rd party libraries in modularized applications is via the “automatic module system”. What this means is that any jar
file that has not explicitly bought into the module system (no module-info.java
), which happens to be on the “module path” will become an “automatic module” where it’s entire contents will be made public
to all other modules and subsequently also has access to all other modules including the “unnamed module”.
The JDK will publish the name of the jar
(excluding version and extension with period spaces between words) as the module name. This way we can reference it as a dependency in our own custom module descriptors.
5.2 Application Modules
These are the orthodox module jar
files which contain a module descriptor and publish their public contract (dependencies and API).
5.3 Platform Modules
These modules are “native” to the JDK and basically form the nett effect of modularizing the JDK. eg: java.base
(implicity dependended on by any module) or java.xml
.
5.4 Unnamed Module
A module that represents the consolidation of all jar
files (modules and non modules) on the class-path and carries no module name, hence “unnamed”. It cannot be referenced by other modules but can access all other modules.
6. Module Descriptor
The module descriptor is the module-info.java
file. It specifies the public API of the module. (ie: what it requires and what it depends on)
Required modules are not transitively available to transitive consumers, (ie: A requires B requires C means A does not see C automatically) unless requires transitive
is specified. Modules are required and packages are exported.
The following example module descriptors include commons-lang3
(generated with the help of jdeps
and patched into the binary commons-lang3
jar
file) and jigsaw.date.service
which specifies a dependency on commons.lang3
.
Commons-lang3 Automatic Module
module commons.lang3 { exports org.apache.commons.lang3; exports org.apache.commons.lang3.builder; exports org.apache.commons.lang3.concurrent; exports org.apache.commons.lang3.event; exports org.apache.commons.lang3.exception; exports org.apache.commons.lang3.math; exports org.apache.commons.lang3.mutable; exports org.apache.commons.lang3.reflect; exports org.apache.commons.lang3.text; exports org.apache.commons.lang3.text.translate; exports org.apache.commons.lang3.time; exports org.apache.commons.lang3.tuple; }
Jigsaw Date Service Application Module
module jigsaw.date.service { requires commons.lang3; exports com.javacodegeeks.jigsaw.date.service; }
7. Tools
A brief primer on some tools available in the JDK which will help us build, package and run our application using Jigsaw features.
7.1 JDeps
A Java command line driven static dependency management tool for jar
files. Results can be filtered and packaged at package
level or jar
file level.
It can be found in the bin
folder of your JDK and has been around since Java 8. Confirming the version of jdeps
is as easy as running jdeps --version
. For help with jdeps
simply run jdeps --help
. Running jdeps commons-lang3-3.4.jar
will reveal all the dependencies of commons-lang3
aggregated by package.
Jdeps output on Commons-Lang3
org.apache.commons.lang3.mutable -> java.io java.base org.apache.commons.lang3.mutable -> java.lang java.base org.apache.commons.lang3.mutable -> org.apache.commons.lang3 commons-lang3-3.4.jar org.apache.commons.lang3.mutable -> org.apache.commons.lang3.math commons-lang3-3.4.jar org.apache.commons.lang3.reflect -> java.lang java.base org.apache.commons.lang3.reflect -> java.lang.annotation java.base org.apache.commons.lang3.reflect -> java.lang.reflect java.base org.apache.commons.lang3.reflect -> java.util java.base org.apache.commons.lang3.reflect -> org.apache.commons.lang3 commons-lang3-3.4.jar org.apache.commons.lang3.reflect -> org.apache.commons.lang3.builder commons-lang3-3.4.jar org.apache.commons.lang3.text -> java.io java.base org.apache.commons.lang3.text -> java.lang java.base org.apache.commons.lang3.text -> java.nio java.base org.apache.commons.lang3.text -> java.text java.base org.apache.commons.lang3.text -> java.util java.base org.apache.commons.lang3.text -> org.apache.commons.lang3 commons-lang3-3.4.jar org.apache.commons.lang3.text -> org.apache.commons.lang3.builder commons-lang3-3.4.jar org.apache.commons.lang3.text.translate -> java.io java.base org.apache.commons.lang3.text.translate -> java.lang java.base org.apache.commons.lang3.text.translate -> java.util java.base org.apache.commons.lang3.text.translate -> org.apache.commons.lang3 commons-lang3-3.4.jar org.apache.commons.lang3.time -> java.io java.base
In the snippet above we can see from left to right:
- left: packages of
commons-lang3
- middle: packages it depends on
- right: modules where the packages (middle) can be found
7.2 JLink
A Java command line driven tool which links / brings together all required modules for an application into a run time image.
This image is usually drastically smaller in size, thus helping reduce the footprint of the application as the entire JRE is not normally needed. jlink
will also resolve static dependencies between modules thus guaranteeing the integrity of each module at run time. jlink
requires that all artifacts are modules with well defined public contracts (exports, requires etc), thus “Automatic” modules will not suffice.
Running jlink --version
reveals the version and running jlink --help
brings up the help menu.
8. Building the Sample Application
In this section we will demonstrate how to build a very simple multi module date service application. The application very conveniently gives us the system date in a nice format using FastDateFormatter
found in commons-lang3
. The application has 2 main modules namely:
jigsaw.date.cli
contains the main-class entry point of the application and depends onjigsaw.date.service
.jigsaw.date.service
which depends oncommons-lang3
. This module defines an interface (DateUtil
), an implementation (SystemDate
) (package private) and a factory (DateUtilFactory
) which constructs the implementation for us. Being Jigsaw we export the package containing the interface and the factory for consumers torequire
and use. Although the implementationSystemDate
is in the same package it is package-private and thus not visible from outside the package.
When you download the sample code and extract it to your file system you will notice 2 main folders, namely maven-build
and manual-build
. We will start with the manual-build
.
8.1. Manual Build
The manual-build
project directory has 2 folders in it, namely before
and after
. after
represents everything completed right up until the creation of a run-time image and thus can be used for reference purposes and is also in fact used in parts of the maven build section. Our focus will be in the before
folder where we will execute a series of instructions to build, package and run our sample application.
Navigating into the before
folder you will see the following structure:
The 2 folders contained within:
- automatic-modules: contains the
commons-lang3-3.4.jar
- src: contains the source code for our project
jigsaw.date.cli
andjigsaw.date.service
and the source code forcommons-lang3-3.4.jar
The next steps will be to compile and build our project manually.
Instructions for building and packaging the project manually
$ javac --module-path automatic-modules -d modules/jigsaw.date.service/ src/jigsaw.date.service/module-info.java src/jigsaw.date.service/com/javacodegeeks/jigsaw/date/service/*.java $ javac --module-path automatic-modules:modules -d modules/jigsaw.date.cli/ src/jigsaw.date.cli/module-info.java src/jigsaw.date.cli/com/javacodegeeks/jigsaw/date/cli/*.java $ java --module-path automatic-modules:modules -m jigsaw.date.cli/com.javacodegeeks.jigsaw.date.cli.Main System date is : 09-09-2017 System date is : 09-09-2017 $ jdeps --generate-module-info tmp automatic-modules/commons-lang3-3.4.jar writing to tmp/commons.lang3/module-info.java $ javac -d tmp/commons.lang3/ --source-path src/3rd-party/commons-lang3/ tmp/commons.lang3/module-info.java tmp/commons.lang3/module-info.java:1: warning: [module] module name component lang3 should avoid terminal digits module commons.lang3 { ^ 1 warning $ mkdir patched-automatic-modules $ cp automatic-modules/commons-lang3-3.4.jar patched-automatic-modules $ jar --update --file patched-automatic-modules/commons-lang3-3.4.jar --module-version=3.3.4-module -C tmp/commons.lang3/ module-info.class $ mkdir application-modules $ jar --create --file=application-modules/jigsaw.date.service@1.0.jar --module-version=1.0 -C modules/jigsaw.date.service/ . $ jar --create --main-class=com.javacodegeeks.jigsaw.date.cli.Main --file=application-modules/jigsaw.date.cli@1.0.jar --module-version=1.0 -C modules/jigsaw.date.cli/ . $ jar --describe-module --file=application-modules/jigsaw.date.service@1.0.jar jigsaw.date.service@1.0 jar:file:///home/jean-jay/Documents/projects/codegeeks/java9-jigsaw-project/manual-build/before/application-modules/jigsaw.date.service@1.0.jar/!module-info.class exports com.javacodegeeks.jigsaw.date.service requires commons.lang3 requires java.base mandated $ jar --describe-module --file=application-modules/jigsaw.date.cli@1.0.jar jigsaw.date.cli@1.0 jar:file:///home/jean-jay/Documents/projects/codegeeks/java9-jigsaw-project/manual-build/before/application-modules/jigsaw.date.cli@1.0.jar/!module-info.class requires java.base mandated requires jigsaw.date.service contains com.javacodegeeks.jigsaw.date.cli main-class com.javacodegeeks.jigsaw.date.cli.Main $ java -p application-modules:patched-automatic-modules -m jigsaw.date.cli System date is : 09-09-2017 System date is : 09-09-2017
- lines 1 & 2: we build the
jigsaw.date.service
andjigsaw.date.cli
projects respectively. Note we specify themodule-path
(where required modules can be found) as theautomatic-modules
folder as we requirecommons-lang3-3.4
. Java 9 (Jigsaw) is nice enough to convert it into an Automatic module. We also specify the output folder for the compiled modules to bemodules
. - lines 4-6: we execute our main class module, again specifying the
module-path
but this time it is a combination ofautomatic-modules
andmodules
. - line 8: we generate a
module-info.java
file forcommons-lang3-3.4.jar
usingjdeps
and place themodule-info.java
file in thetmp
folder. - line 11: we compile our
module-info.java
file into amodule-info.class
file using the source code ofcommons-lang3-3.4.jar
located in thesrc
folder. - line 18: we copy the original
commons-lang3-3.4.jar
file from theautomatic-modules
folder to thepatched-automatic-modules
folder after creating said folder. - line 20: we update the
commons-lang3-3.4.jar
insidepatched-automatic-modules
with the compiledmodule-info.class
file. - line 24 & 25: we make 2
jar
files from our application modulesjigsaw.date.service
andjigsaw.date.cli
. - lines 27-38: we use the
jar describe
utility to describe the modules and their API’s to us for confirmation. - lines 40-42: we run the modules.
The creation of a module-info.java
and subsequent compilation of said module descriptor and patching of the commons-lang3-3.4.jar
is only required for the run time image we will be creating at the end of the tutorial.
8.2 Maven Build
The maven-build
project directory has the following structure to it: (excluding target
as the target
folder will be generated on maven build)
Before running any build we need to publish the patched automatic jar (commons-lang3-3.4.jar
) from the manual-build
project to our local maven repository. This must be done so that when we run the maven build mvn clean install package
maven will use the adjusted commons-lang3-3.4.jar
(contains a module-info.class
) for our maven-build
version of the project.
The patched automatic jar can be found in <project-root-folder>/manual-build/after/patched-automatic-modules/commons-lang3-3.4.jar
and can be installed to our local maven repository with the following command: (replace file path appropriately)
Installing our patched automatic module
mvn install:install-file -Dfile=<path-toproject>/java9-jigsaw-project/manual-build/after/patched-automatic-modules/commons-lang3-3.4.jar -DgroupId=org.apache.commons -DartifactId=commons-lang3 -Dversion=3.4-module -Dpackaging=jar
Snippets of the pom.xml
files are following for the parent maven project and each maven module:
Snippet of Jigsaw Date pom.xml (Parent project)
... <modules> <module>jigsaw-date-cli</module> <module>jigsaw-date-service</module> </modules> <properties> <maven.compiler.plugin>3.6.0</maven.compiler.plugin> <maven.toolchains.plugin>1.1</maven.toolchains.plugin> <maven.jar.plugin>2.3.1</maven.jar.plugin> <maven.dependency.plugin.version>3.0.1</maven.dependency.plugin.version> <maven.compiler.source>1.9</maven.compiler.source> <maven.compiler.target>1.9</maven.compiler.target> <maven.compiler.release>9</maven.compiler.release> </properties> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>${maven.compiler.plugin}</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-toolchains-plugin</artifactId> <version>${maven.toolchains.plugin}</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>${maven.jar.plugin}</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>${maven.dependency.plugin.version}</version> </plugin> </plugins> </pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-toolchains-plugin</artifactId> <configuration> <toolchains> <jdk> <version>1.9</version> <vendor>oracle</vendor> </jdk> </toolchains> </configuration> <executions> <execution> <?m2e ignore ?> <goals> <goal>toolchain</goal> </goals> </execution> </executions> </plugin> </plugins> </build> ...
Snippet of Jigsaw Date Service pom.xml
... <properties> <org.apache.commons.lang.version>3.4-module</org.apache.commons.lang.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>${org.apache.commons.lang.version}</version> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <outputDirectory>${project.build.directory}/../../target/modules</outputDirectory> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <id>copy-dependencies</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory>${project.build.directory}/../../target/modules</outputDirectory> <overWriteReleases>false</overWriteReleases> <overWriteSnapshots>false</overWriteSnapshots> <overWriteIfNewer>true</overWriteIfNewer> </configuration> </execution> </executions> </plugin> </plugins> </build> ...
- line 3: we specify a dependency on the patched automatic module
- line 29 & 43: we specify that the build artifact and any dependencies be copied to the
<project-root-folder>/maven-build/target/modules
Snippet of Jigsaw Date Cli pom.xml
... <dependencyManagement> <dependencies> <dependency> <groupId>com.javacodegeeks</groupId> <artifactId>jigsaw-date-service</artifactId> <version>${project.version}</version> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>com.javacodegeeks</groupId> <artifactId>jigsaw-date-service</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <outputDirectory>${project.build.directory}/../../target/modules</outputDirectory> <archive> <manifest> <mainClass>com.javacodegeeks.jigsaw.date.cli.Main</mainClass> </manifest> </archive> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <id>copy-dependencies</id> <phase>package</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory>${project.build.directory}/../../target/modules</outputDirectory> <overWriteReleases>false</overWriteReleases> <overWriteSnapshots>false</overWriteSnapshots> <overWriteIfNewer>true</overWriteIfNewer> </configuration> </execution> </executions> </plugin> </plugins> </build> ...
- lines 12-17: we specify a dependency on the
jigsaw.date.service
module - line 29 & 43: we specify that the build artifact and any dependencies be copied to the
<project-root-folder>/maven-build/target/modules
Ensure you are in the <project-root-folder>/maven-build
and execute mvn clean install package
. The project will build and all built artifacts will be deposited into <project-root-folder>/maven-build/target/modules
. You should see 3 module jars if successful:
jigsaw-date-cli-0.0.1-SNAPSHOT.jar
jigsaw-date-service-0.0.1-SNAPSHOT.jar
commons-lang3-3.4-module.jar
9. Running the Sample Application
Running the maven-build
version (once built) can be done by navigating into the following folder <project-root-folder>/maven-build
and executing:
Running maven built project
java -jar --module-path target/modules -m jigsaw.date.cli/com.javacodegeeks.jigsaw.date.cli.Main System date is : 09-09-2017 System date is : 09-09-2017
10. Runtime Image
In this section we will create a run-time image from our manual-build
project. This can only be done once all the previous steps for the manual build have completed successfully, therefore we will use the after
folder in manual-build
which guarantees all previous steps have been completed successfully.
Once the image has been built a new folder image.jigsaw.date.cli
should have been created in the after
folder. On my system the folder size is roughly 47.3mb proving how much jlink
can drastically reduce the size of a Java run time image needed.
Building and Running a Runtime Image
$ jlink -v --module-path $JAVA_HOME/jmods:patched-automatic-modules:modules --add-modules jigsaw.date.cli,jigsaw.date.service --output image.jigsaw.date.cli --launcher launch=jigsaw.date.cli/com.javacodegeeks.jigsaw.date.cli.Main commons.lang3 file:///home/jean-jay/Documents/projects/codegeeks/java9-jigsaw-project/manual-build/after/patched-automatic-modules/commons-lang3-3.4.jar java.base file:///home/jean-jay/runtimes/jdk-9/jmods/java.base.jmod jigsaw.date.cli file:///home/jean-jay/Documents/projects/codegeeks/java9-jigsaw-project/manual-build/after/modules/jigsaw.date.cli/ jigsaw.date.service file:///home/jean-jay/Documents/projects/codegeeks/java9-jigsaw-project/manual-build/after/modules/jigsaw.date.service/ Providers: java.base provides java.nio.file.spi.FileSystemProvider used by java.base $ ./image.jigsaw.date.cli/bin/java --list-modules commons.lang3@3.3.4-module java.base@9 jigsaw.date.cli jigsaw.date.service $ ./image.jigsaw.date.cli/bin/launch System date is : 09-09-2017 System date is : 09-09-2017
- line 1: we issue the
jlink
command specifying the module path including our application module, patched automatic module and the JDK (jmod / platform modules). We also specify the output folderimage.jigsaw.date.cli
and a launcher script calledlauncher
pointing to our main class. The launcher script will allow us to conveniently run our application from it. Because we specified verbose output we will see all the modules being added to the image and the locations from where they were taken being output to the screen. - line 9: we call the
java
executable from inside the image and ask it to list it’s modules for confirmation. - line 14: we call the
launch
script inside the image and we can see our system date being output in a nice formatted way.
11. Summary
In this tutorial we covered what project Jigsaw involves and what it brings to the Java platform. We dived into the mechanics of it and demonstrated how to structure, build and package an example project both from the command line and using the popular build an dependency management tool, Maven.
12. Download the Source Code
This was a Java 9 Jigsaw Project Tutorial
You can download the full source code of this example here: Java 9 Jigsaw Project Tutorial