Java 9 Jigsaw Project Tutorial (original) (raw)

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.

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:

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:

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

jdk 1.9 oracle /home/jean-jay/runtimes/jdk-9 jdk 1.8 oracle /home/jean-jay/runtimes/jdk1.8.0_101

4. Goals of Jigsaw

The goals of Jigsaw are:

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):

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:

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:

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:

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:

Manual directory Structure (before folder)

The 2 folders contained within:

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

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)

Directory structure of the maven-build project

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=/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.xmlfiles are following for the parent maven project and each maven module:

Snippet of Jigsaw Date pom.xml (Parent project)

... jigsaw-date-cli jigsaw-date-service

<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

... <org.apache.commons.lang.version>3.4-module</org.apache.commons.lang.version>

<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>

...

Snippet of Jigsaw Date Cli pom.xml

... com.javacodegeeks jigsaw-date-service ${project.version}

<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>

...

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:

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

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