Snippet: Java 9 Modules and JPMS

April 17th, 2017 by

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.

Modules Component-Diagram

Modules Component-Diagram

 

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.

Modules Component-Diagram

Modules Component-Diagram

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

Tags: , , , , , , , , , ,

One Response to “Snippet: Java 9 Modules and JPMS”

  1. vamsi Says:

    gr8 article thanks

    Can you please add further content on how to use maven and gradle on top this example.

Search
Categories