Snippet: Java 9 Modules and JPMS
April 17th, 2017 by Micha KopsPlaying 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.
Contents
Prerequisites and Setup
We need an early access build of the Java (TM) 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 (TM) 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 Bitbucket repository, fork it there or clone it using Git:
git clone https://bitbucket.org/hascode/java9-module-tutorial.git
Resources
- Project Jigsaw: Module System Quick-Start Guide
- JEP 261: Module System
- JEP 200: The Modular JDK
- JEP 282: jlink: The Java Linker
- JEP 220: Modular Run-Time Images
- JEP 260: Encapsulate Most Internal APIs
- Scott Stark’s Blog: Critical Deficiencies in Jigsaw(JSR-376, Java Platform Module System)
- Mike Hearn: Is Jigsaw good or is it wack?
- Stephen Colebourne: Java Modules – JPMS Basics
Tags: compiler, jar, java9, jdk, jep261, jigsaw, jlink, jpms, jsr376, module, osgi
January 27th, 2018 at 6:31 am
gr8 article thanks
Can you please add further content on how to use maven and gradle on top this example.