Playing around with the new module system in Java 9 I simply wanted to write down how to achieve the most basic tasks.
Therefore I created the following module how-to based upon a simple demonstration project consisting of two dependant modules.
Prerequisites and Setup
We need an early access build of the Java ™ 9 JDK, available for download here.
In addition we should make sure, that our environment variable JAVA_HOME is set to the corresponding directory and calling java -version returns something similar to this:
$ java -version
java version "9-ea"
Java(TM) SE Runtime Environment (build 9-ea+163)
Java HotSpot(TM) 64-Bit Server VM (build 9-ea+163, mixed mode)
Now we’re creating a new directory for our code and in there a directory named src for our sources, a directory named mods for our compiled modules and a directory named mlib for our assembled module jar-files.
.
├── mlib
├── mods
└── src
Now we’re ready to create some modules.
Creating Modules and Modelling Dependencies
We’re going to create two modules here, for each of them, we’re creating a directory with the full module name in our src directory and add the module-info.java containing the module description.
Module 1: DateTool
Our first module named com.hascode.datetool offers a simple API to return the current date as a string. Therefore it exports an interface DateTool and a factory to create instances of this control, named DateToolFactory. Its default implementation of DateTool named DateToolImpl is not exported. We’re using the packages com.hascode.datetool.api and com.hascode.datetool.internal to separate both.
This is the module-info.java, we’re specifying the module and declaring the package com.hascode.datetool.api to be available for use in other modules:
module com.hascode.datetool {
exports com.hascode.datetool.api;
}
This is what our first modules directory structure looks like now:
src
└── com.hascode.datetool
├── com
│ └── hascode
│ └── datetool
│ ├── api
│ │ ├── DateToolFactory.java
│ │ └── DateTool.java
│ └── internal
│ └── DateToolImpl.java
└── module-info.java
Now we’re ready to write another module using our datetool module.
Module 2: Sample Application
Our second module is named com.hascode.sample and that’s again what we’re using as directory name for the module.
This is our module info where we’re declaring the sample module and its dependency to the datetool module:
module com.hascode.sample {
requires com.hascode.datetool;
}
The following main-class prints the current date using the module-referenced datetool APIs:
package com.hascode.sample;
import com.hascode.datetool.api.DateTool;
import com.hascode.datetool.api.DateToolFactory;
public class Main {
public static void main(String[] args){
DateTool dt = DateToolFactory.create();
System.out.printf("The time is %s", dt.currentDate());
}
}
Our second modules directory structure now looks like this:
src
└── com.hascode.sample
├── com
│ └── hascode
│ └── sample
│ └── Main.java
└── module-info.java
So overall our final directory structure looks like this:
.
├── mlib
├── mods
└── src
├── com.hascode.datetool
│ ├── com
│ │ └── hascode
│ │ └── datetool
│ │ ├── api
│ │ │ ├── DateToolFactory.java
│ │ │ └── DateTool.java
│ │ └── internal
│ │ └── DateToolImpl.java
│ └── module-info.java
└── com.hascode.sample
├── com
│ └── hascode
│ └── sample
│ └── Main.java
└── module-info.java
Compiling Modules
We may now compile our modules using the following command:
javac -d mods --module-source-path src $(find src -name "*.java")
Afterwards the compiled modules should be visible in the mods directory specified so that our project structure now looks like this:
.
├── mlib
├── mods
│ ├── com.hascode.datetool
│ │ ├── com
│ │ │ └── hascode
│ │ │ └── datetool
│ │ │ ├── api
│ │ │ │ ├── DateTool.class
│ │ │ │ └── DateToolFactory.class
│ │ │ └── internal
│ │ │ └── DateToolImpl.class
│ │ └── module-info.class
│ └── com.hascode.sample
│ ├── com
│ │ └── hascode
│ │ └── sample
│ │ └── Main.class
│ └── module-info.class
└── src
├── com.hascode.datetool
│ ├── com
│ │ └── hascode
│ │ └── datetool
│ │ ├── api
│ │ │ ├── DateToolFactory.java
│ │ │ └── DateTool.java
│ │ └── internal
│ │ └── DateToolImpl.java
│ └── module-info.java
└── com.hascode.sample
├── com
│ └── hascode
│ └── sample
│ └── Main.java
└── module-info.java
We may now run our application from the compiled modules..
Running from compiled Module
We may run the Main.java now using the directory mods as module-path using the following command:
java --module-path mods -m com.hascode.sample/com.hascode.sample.Main
The time is 2017-04-17T14:45:34.248930%
Creating Module Jars
In this step we want to create module jars for our two modules and we do so using the following commands:
jar --create --file=mlib/com.hascode.datetool@1.0.jar -C mods/com.hascode.datetool .
jar --create --file=mlib/com.hascode.sample@1.0.jar --main-class=com.hascode.sample.Main -C mods/com.hascode.sample .
For the second jar-file we’re specifying a main-class to simplify running com.hascode.sample.Main later.
Afterwards we should see the following two jar-files in the mlib directory:
mlib
├── com.hascode.datetool@1.0.jar
└── com.hascode.sample@1.0.jar
Running Application from Module Jar
We may now run our sample application using the module jar-files as this:
$ java -p mlib -m com.hascode.sample
The time is 2017-04-17T14:57:06.778803%
Read Module Descriptor from Jar
Using the jar command we’re able to read module information from the two jar-files we have built.
$ jar -d --file=mlib/com.hascode.datetool@1.0.jar
module com.hascode.datetool (module-info.class)
requires mandated java.base
exports com.hascode.datetool.api
contains com.hascode.datetool.internal
$ jar -d --file=mlib/com.hascode.sample@1.0.jar
module com.hascode.sample (module-info.class)
requires com.hascode.datetool
requires mandated java.base
contains com.hascode.sample
main-class com.hascode.sample.Main
Create Modular Run-Time Image
In the last step we’re going to create a modular run-time image as specified in JEP-200 including only needed modules and their transitive dependencies using the new jlink utility tool:
jlink --module-path $JAVA_HOME/jmods:mlib --add-modules com.hascode.sample --output sampleapp
Afterwards we should see a similar structure in the directory sampleapp:
sampleapp
├── bin
│ ├── java
│ └── keytool
├── conf
│ ├── net.properties
│ └── security
│ ├── java.policy
│ ├── java.security
│ └── policy
│ ├── limited
│ │ ├── default_local.policy
│ │ ├── default_US_export.policy
│ │ └── exempt_local.policy
│ ├── README.txt
│ └── unlimited
│ ├── default_local.policy
│ └── default_US_export.policy
├── include
│ ├── classfile_constants.h
│ ├── jni.h
│ ├── jvmticmlr.h
│ ├── jvmti.h
│ └── linux
│ └── jni_md.h
├── legal
│ └── java.base
│ ├── aes.md
│ ├── asm.md
│ ├── cldr.md
│ ├── COPYRIGHT
│ ├── icu.md
│ └── zlib.md
├── lib
│ ├── classlist
│ ├── jexec
│ ├── jli
│ │ └── libjli.so
│ ├── jrt-fs.jar
│ ├── jvm.cfg
│ ├── libjava.so
│ ├── libjimage.so
│ ├── libjsig.so
│ ├── libnet.so
│ ├── libnio.so
│ ├── libverify.so
│ ├── libzip.so
│ ├── modules
│ ├── security
│ │ ├── blacklist
│ │ ├── blacklisted.certs
│ │ ├── cacerts
│ │ ├── default.policy
│ │ └── trusted.libraries
│ ├── server
│ │ ├── libjsig.so
│ │ ├── libjvm.so
│ │ └── Xusage.txt
│ └── tzdb.dat
└── release
The resulting run-time with all binaries and stuff included is only 44MB big.
To finally verify that our modules were included, we may now use the following command from the Java ™ binary in sampleapp/bin to list the modules:
$ ./sampleapp/bin/java --list-modules
com.hascode.datetool
com.hascode.sample
java.base@9-ea
Tutorial Sources
Please feel free to download the tutorial sources from my GitHub repository, fork it there or clone it using Git:
git clone https://github.com/hascode/java9-module-tutorial.git