Java 9 Modules Quick Start Example

[Updated: Oct 21, 2017, Created: Sep 21, 2017]

Java 9 Platform Module System (JPMS) introduces the concept of named 'module'.

What are Java Modules?

Similar to Java packages, modules introduce another level of Java artifacts groupings. Each such group (module) encloses a number of packages. A module is declared by adding a file module-info.java at the root of source code. This file uses following syntax:

 module abc.xyz{
  ....
 }

Where abc.xyz declares the module name.

The 'exports' clause

The file module-info.java may specify what public classes of a package it wants to make visible to outside world. It does so by using a new keyword exports.

 module abc.xyz{
  exports com.foo.bar;
 }

Where com.foo.bar represents a package.

It is important to understand that even if classes in a given package are public, they cannot be visible outside (both at compile time and runtime) if their packages are not explicitly exported. Modules, therefore, can hide public functionality and that improves Java encapsulation system.

The 'requires' clause

Another module which wants to use the exported packages can use another new keyword requires in its module-info.java file to import (read) the target module.

module def.stu{
 requires abc.xyz;
}

The Module system introduces other various features which we will be exploring in this series of tutorials. In the following example, we will show how to create modules and use 'exports' and 'requires' clauses. We will use 'java' and 'javac' command line tools to build and run the classes to get familiar with the new module-related options introduced with these tools. We are going to use JDK 9 general availability release.


Example

In this example, we will create two modules 'common.widget' and 'data.widget' and will place them under a single folder 'modules-examples/src'. The file 'module-info.java' will be placed under each of the main folder of the modules.

First Module

modules-example/src/common.widget/org/jwidgets/SimpleRenderer.java

package org.jwidgets;

public class SimpleRenderer {
  public void renderAsString(Object object) {
      System.out.println(object);
  }
}

modules-example/src/common.widget/com/logicbig/RendererSupport.java

package com.logicbig;

import org.jwidgets.SimpleRenderer;

public class RendererSupport {
  public void render(Object object) {
      new SimpleRenderer().renderAsString(object);
  }
}

modules-example/src/common.widget/module-info.java

module common.widget{
  exports com.logicbig;
}

Second Module

modules-example/src/data.widget/com/example/Component.java

package com.example;

import com.logicbig.RendererSupport;

public class Component {
  public static void main(String[] args) {
      RendererSupport support = new RendererSupport();
      support.render("Test Object");
  }
}

modules-example/src/data.widget/module-info.java

module data.widget {
  requires common.widget;
}

Compiling with javac

I have already install JDK 9 (the general availability release) and set it on the system path. We are going to use Windows cmd to execute Java tools.

D:\modules-example>java -version
java version "9"
Java(TM) SE Runtime Environment (build 9+181)
Java HotSpot(TM) 64-Bit Server VM (build 9+181, mixed mode)

Our project has following structure:

D:\modules-example>tree /F /A
\---src
+---common.widget
| | module-info.java
| |
| +---com
| | \---logicbig
| | RendererSupport.java
| |
| \---org
| \---jwidgets
| SimpleRenderer.java
|
\---data.widget
| module-info.java
|
\---com
\---example
Component.java

When it comes to compiling multiple java classes from command line, we can conveniently use the @filename option of the javac command to include a file that lists the source file paths. We are going to save our first module's java classes list in a file by using windows 'dir' command:

D:\modules-example>dir  /B  /S  src\common.widget\*.java > src1.txt
D:\modules-example>type src1.txt
D:\modules-example\src\common.widget\module-info.java
D:\modules-example\src\common.widget\com\logicbig\RendererSupport.java
D:\modules-example\src\common.widget\org\jwidgets\SimpleRenderer.java
D:\modules-example>javac -d out/common.widget @src1.txt

On a Linux system, we can alternatively use the followings:

$ find src/common.widget/ -name *.java > sources.txt
$ javac @sources.txt

Similarly, we are going to compile our second module:

D:\modules-example>dir  /B  /S  src\data.widget\*.java > src2.txt
D:\modules-example>javac --module-path out -d out/data.widget @src2.txt

This time we specified new javac option '--module-path' with the value 'out' (our output folder). we can also specify a short form of this option as '-p out' . This option declares where to find application modules. In this case we needed to specify this option because 'data.widget' is using 'common.widget' classes so compiler needs to resolve the references.

After compilation

D:\modules-example>tree /F /A
| src1.txt
| src2.txt
|
+---out
| +---common.widget
| | | module-info.class
| | |
| | +---com
| | | \---logicbig
| | | RendererSupport.class
| | |
| | \---org
| | \---jwidgets
| | SimpleRenderer.class
| |
| \---data.widget
| | module-info.class
| |
| \---com
| \---example
| Component.class
|
\---src
+---common.widget
| | module-info.java
| |
| +---com
| | \---logicbig
| | RendererSupport.java
| |
| \---org
| \---jwidgets
| SimpleRenderer.java
|
\---data.widget
| module-info.java
|
\---com
\---example
Component.java

Note that module-info.java is compiled into module-info.class as well.

Running the main class

D:\modules-example>java --module-path out -m data.widget/com.example.Component
Test Object

'java' command also has the new option '--module-path' (short form -p ). This is to specify what directories (semicolon-separated if there are many) to be searched for the modules.

The -m option specifies the module where our main class is to be executed. Here is the general format of this option:

-m module[/mainclass] or --module modulename[/mainclass]

Attempting to use non-exported classes

In our example, since the package 'org.jwidgets' has not been exported by 'common.widget' module, 'data.widget' cannot use classes under that package. Let's try doing that and see what happens:

package com.example;
import org.jwidgets.SimpleRenderer;

public class Component {
  public static void main(String[] args) {
    SimpleRenderer simpleRenderer = new SimpleRenderer();
    simpleRenderer.renderAsString("Test Object");
  }
}

Now let's try to compile 'data.widget' module again:

D:\modules-example>javac --module-path out -d out/data.widget @src2.txt
D:\modules-example\src\data.widget\com\example\Component.java:3: error: package org.jwidgets is not visible import org.jwidgets.SimpleRenderer; ^ (package org.jwidgets is declared in module common.widget, which does not export it) 1 error

Module Path

As seen in above example, Java Module system uses module path instead of class path to locate the modules. The module path solves many issues of class path; most notably issues like making no distinction between the loaded artifacts, no reporting of missing artifacts in advance, allowing conflicts between same classes of different version residing in the different jars on the class path. As opposed to class path, the module path locates whole modules rather than individual classes.

JDK API and Modules

The module system has been applied to JDK to itself. Now Java classes are grouped into modules then into packages. check out Java API documents. All modules implicitly import java.base. I will also suggest to browse though <jdk-home>/src.zip to see how modules are organised.

Example Project

Dependencies and Technologies Used :

  • JDK 9
Java 9 Modules Quick Start Example Select All Download
  • modules-example
    • src
      • common.widget
        • com
          • logicbig
        • org
          • jwidgets
      • data.widget
        • com
          • example

See Also