Java 9. Project Jigsaw. Modularity

The key feature of the upcoming release of Java 9 is to support modularity, which will bring Project Jigsaw. The purpose of the project is to make the Java SE platform more flexible, productive and protected due to splitting JDK onto the modules and introduce a modular system.

Module

Unlike the usual jar file, which in fact is a code and resource storage for JVM, jar module contains a class module-info which provides:

  • module name;
  • list of module dependencies necessary for the correct compilation and executing;
  • information about packages exported by this module;
  • list of services that provides module in runtime.

The third item brings us a change, which previously lacked. Now, if class is declared as public, it doesn’t mean that it will be accessible to all modules. This change extends public scope:

  • public class in exported package – accessible to all dependent modules which read the current module;
  • public class in package which is exported to the specified module – accessible only to the specified module;
  • public class without package exporting – accessible to all classes of the current module.

Here is an example of module-info.java:

module com.example.samplemodule {
requires com.example.sampleapp;
requires public java.httpclient;
exports com.example.samplemodule.model;
exports com.example.samplemodule.spi;
uses com.example.samplemodule.spi.DataProvider;
provides com.example.sampleapp.spi.SettingsProvider
with com.example.samplemodule.ModuleSettingsProvider;
}

Let’s go over this line by line:

module com.example.samplemodule {

This module has name com.example.samplemodule (such package naming is used to avoid conflicts).

requires com.example.sampleapp;
requires public java.httpclient;

It depends on com.example.sampleapp, java.httpclient and java.base modules (the last one is default for all modules). java.httpclient is dependent for all modules that use com.example.samplemodule.

exports com.example.samplemodule.model;
exports com.example.samplemodule.spi;

The module exports com.example.samplemodule.model and com.example.samplemodule.spi packages, so all public classes in these packages will be accessible to other modules which depend on it.

uses com.example.samplemodule.spi.DataProvider;

The module uses com.example.samplemodule.spi.DataProvider to retrieve data from the other modules.

provides com.example.sampleapp.spi.SettingsProvider
with com.example.samplemodule.ModuleSettingsProvider;

Also it provides settings to service of other module, implementing interface com.example.sampleapp.spi.SettingsProvider in class com.example.samplemodule.ModuleSettingsProvider.

Let’s start practice.

Example 1. Direct dependence of two modules

We will have two projects: TimeApp – the main application that displays the time provided by the second project – TimeLocalModule.

In this example TimeApp in the main class will call the method of the public class of second module directly. To do this, we have to declare a dependency on the second module in the main module and export the package with class, that provides current time info. Otherwise, we’re unable to import the package of the second module.

TimeApp

// com/example/timeapp/Main.java
package com.example.timeapp;
import com.example.timelocal.TimeLocal;
public final class Main {

public static void main(String[] args) {
System.out.format("Current time: %s%n", TimeLocal.now());
}
}
// module-info.java
module com.example.timeapp {
requires java.base;
requires com.example.timelocalmodule;
}

TimeLocalModule

// com/example/timelocal/TimeLocal.java
package com.example.timelocal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class TimeLocal {

public static String now() {
return DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now());
}
}
// module-info.java
module com.example.timelocalmodule {
exports com.example.timelocal;
}

In the main project settings we need to add a dependency on TimeLocalModule project:

Adding dependency
Adding dependency

NetBeans shows package scope, com.example.timelocal is now public:

Package scope in Netbeans IDE

To compile and run from the command line you can use this script:

@echo off
set JAVA9_HOME=D:\Program Files\Java\jdk-9
set JAVA9="%JAVA9_HOME%/bin/java"
set JAVAC9="%JAVA9_HOME%/bin/javac"
mkdir mods\com.example.timeapp
mkdir mods\com.example.timelocalmodule
echo Compile timelocalmodule
%JAVAC9% -d mods/com.example.timelocalmodule ^
TimeLocalModule/src/module-info.java ^
TimeLocalModule/src/com/example/timelocal/TimeLocal.java
echo Compile timeapp
%JAVAC9% --module-path mods -d mods/com.example.timeapp ^
TimeApp/src/module-info.java ^
TimeApp/src/com/example/timeapp/Main.java
echo Run timeapp
%JAVA9% --module-path mods ^
-m com.example.timeapp/com.example.timeapp.Main
#!/bin/bash
JAVA9_HOME=/usr/lib/jvm/java-9-oracle
JAVA9=$JAVA9_HOME/bin/java
JAVAC9=$JAVA9_HOME/bin/javac
mkdir -p mods/com.example.timeapp
mkdir -p mods/com.example.timelocalmodule
echo "Compile timelocalmodule"
$JAVAC9 -d mods/com.example.timelocalmodule \
TimeLocalModule/src/module-info.java \
TimeLocalModule/src/com/example/timelocal/TimeLocal.java
echo "Compile timeapp"
$JAVAC9 --module-path mods -d mods/com.example.timeapp \
TimeApp/src/module-info.java \
TimeApp/src/com/example/timeapp/Main.java
echo "Run timeapp"
$JAVA9 --module-path mods \
-m com.example.timeapp/com.example.timeapp.Main

Or run in NetBeans IDE. Here is result:

Current time: 2016-10-20T18:36:36.6763098

Example 1 on GitHub

Example 2. Services and ServiceLoader

The previous example demonstrates a new way of connecting libraries, now we will make the real module. Modify our example so that the main application becomes dependency to other modules. At compile time it won’t know anything about modules that extend functionality, but at runtime it will take available extensions.

Available implementations previously were in the text file at META-INF/services/exampleservice, ServiceLoader read out names of classes and provided implementations of interfaces and abstract classes in iterator. Now it is possible to set implementation in module-info of additional modules:

module additional {
provides com.example.spi.Provider // base interface
with com.impl.ProviderImpl; // implemented in this module
}

For usage add this line to the main module:

module main {
uses com.example.spi.Provider;
}

And process received implementations:

ServiceLoader<Provider> sl = ServiceLoader.load(Provider.class);
for (Provider p : sl) {
// ..
}

Unlike the old approach, now dependencies are checked at compile time.

Let’s create a separate package com.example.timeapp.spi, which will be public for module dependencies. It will contain the interface, which will be implemented by other modules to provide information.

TimeApp

// com/example/timeapp/spi/TimeProvider.java
package com.example.timeapp.spi;
public interface TimeProvider {

String now();
}
// module-info.java
module com.example.timeapp {
requires java.base;

exports com.example.timeapp.spi;
}

Don’t forget to remove the dependence on the module in project properties, otherwise you receive cyclic dependence.

TimeLocalModule

// com/example/timelocal/TimeLocalProvider.java
package com.example.timelocal;
import com.example.timeapp.spi.TimeProvider;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class TimeLocalProvider implements TimeProvider {

@Override
public String now() {
return DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now());
}
}
// module-info.java
module com.example.timelocalmodule {
requires com.example.timeapp;

provides com.example.timeapp.spi.TimeProvider
with com.example.timelocal.TimeLocalProvider;
}

Now specify that main application should load modules providing TimeProvider implementations in runtime. There is a special system loading system –ServiceLoader class. By passing provider class and registering this class in module-info with keyword uses, we can get a list of implementations available in runtime.

TimeApp

// com/example/timeapp/Main.java
package com.example.timeapp;
import com.example.timeapp.spi.TimeProvider;
import java.util.ServiceLoader;
public final class Main {

public static void main(String[] args) {
ServiceLoader<TimeProvider> serviceLoader = ServiceLoader.load(TimeProvider.class);
serviceLoader.forEach(t -> {
System.out.format("Current time: %s%n", t.now());
System.out.println(t.getClass());
});
}
}

If we now run the application, we will get the error:

Exception in thread "main" java.util.ServiceConfigurationError: com.example.timeapp.spi.TimeProvider: use not declared in module com.example.timeapp
at java.util.ServiceLoader.fail(java.base@9-ea/ServiceLoader.java:386)
at java.util.ServiceLoader.checkModule(java.base@9-ea/ServiceLoader.java:371)
at java.util.ServiceLoader.<init>(java.base@9-ea/ServiceLoader.java:319)
at java.util.ServiceLoader.<init>(java.base@9-ea/ServiceLoader.java:351)
at java.util.ServiceLoader.load(java.base@9-ea/ServiceLoader.java:1021)
at com.example.timeappMain.main(com.example.timeapp/Main.java:9)

This is because we have not registered a class in module-info. Let’s do it:

// module-info.java
module com.example.timeapp {
requires java.base;

exports com.example.timeapp.spi;

uses com.example.timeapp.spi.TimeProvider;
}

Now the problem has been resolved, but data doesn’t appear. That’s because the modules aren’t added to dependences.

In NetBeans IDE you need to add the module to Modulepath as single jar file, not Java Project (Project Properties – Run tab). Don’t forget to compile module before adding (Clean and Build).

Adding a module

Run and here is the output:

Current time: 2016-10-20T20:41:39.9614732
class com.example.timelocal.TimeLocalProvider

We can add another module that will take the information from network.

TimeNetworkModule

// com/example/timenetwork/TimeNetworkProvider.java
package com.example.timenetwork;
import com.example.timeapp.spi.TimeProvider;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpResponse;
public class TimeNetworkProvider implements TimeProvider {

@Override
public String now() {
try {
return HttpClient.getDefault()
.request(URI.create("http://www.timeapi.org/utc/now"))
.header("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36")
.GET()
.response()
.body(HttpResponse.asString());
} catch (IOException | InterruptedException ex) {
throw new RuntimeException("Network error");
}
}
}
// module-info.java
module com.example.timenetworkmodule {
requires com.example.timeapp;
requires java.httpclient;

provides com.example.timeapp.spi.TimeProvider
with com.example.timenetwork.TimeNetworkProvider;
}

We added java.httpclient dependency to the new module for working with Java 9 HTTP/2 client.

Let’s compile and run the project in NetBeans IDE or from the command line:

rem compile.cmd
echo Compile timeapp
%JAVAC9% -d mods/com.example.timeapp ^
TimeApp/src/module-info.java TimeApp/src/com/example/timeapp/Main.java ^
TimeApp/src/com/example/timeapp/spi/TimeProvider.java
echo Compile timelocalmodule
%JAVAC9% --module-path mods -d mods/com.example.timelocalmodule ^
TimeLocalModule/src/module-info.java ^
TimeLocalModule/src/com/example/timelocal/TimeLocal.java ^
TimeLocalModule/src/com/example/timelocal/TimeLocalProvider.java
echo Compile timenetworkmodule
%JAVAC9% --module-path mods -d mods/com.example.timenetworkmodule ^
TimeNetworkModule/src/module-info.java ^
TimeNetworkModule/src/com/example/timenetwork/TimeNetworkProvider.java
echo Run timeapp
%JAVA9% --module-path mods ^
-m com.example.timeapp/com.example.timeapp.Main
# compile.sh
echo "Compile timeapp"
$JAVAC9 -d mods/com.example.timeapp \
TimeApp/src/module-info.java \
TimeApp/src/com/example/timeapp/Main.java \
TimeApp/src/com/example/timeapp/spi/TimeProvider.java
echo "Compile timelocalmodule"
$JAVAC9 --module-path mods -d mods/com.example.timelocalmodule \
TimeLocalModule/src/module-info.java \
TimeLocalModule/src/com/example/timelocal/TimeLocal.java \
TimeLocalModule/src/com/example/timelocal/TimeLocalProvider.java
echo "Compile timenetworkmodule"
$JAVAC9 --module-path mods -d mods/com.example.timenetworkmodule \
TimeNetworkModule/src/module-info.java \
TimeNetworkModule/src/com/example/timenetwork/TimeNetworkProvider.java
echo "Run timeapp"
$JAVA9 --module-path mods \
-m com.example.timeapp/com.example.timeapp.Main

Now we get:

Current time: 2016-10-20T20:55:03.3944269
class com.example.timelocal.TimeLocalProvider
Current time: 2016-10-20T17:55:06+00:00
class com.example.timenetwork.TimeNetworkProvider

Example 2 on GitHub

Example 3. Modules and resources

According to Modular System Requirements, module resources should be directly accessible only by code within that module. It means we should load them only in the module, process and pass data to another modules.

Let’s create a simple form with a buttons for each module. Clicking the button displays the time.

You need to add java.desktop dependency to the main application to import java.awt and java.swing packages.

// module-info.java
module com.example.timeapp {
requires java.base;
requires java.desktop;

exports com.example.timeapp.spi;

uses com.example.timeapp.spi.TimeProvider;
}

Edit TimeProvider to be able to get the module icon:

// com/example/timeapp/spi/TimeProvider.java
package com.example.timeapp.spi;
import java.awt.Image;
public interface TimeProvider {

String now();

Image icon();
}
// com/example/timeapp/Main.java
public final class Main extends JFrame {

public static void main(String[] args) {
final Main frame = new Main();
ServiceLoader<TimeProvider> serviceLoader = ServiceLoader.load(TimeProvider.class);
serviceLoader.forEach(t -> {
final JButton button = new JButton();
button.setText(t.getClass().getSimpleName());
final Image icon = t.icon();
if (icon != null) {
button.setIcon(new ImageIcon(icon));
}
button.addActionListener(e -> {
frame.outputLabel.setText(String.format("Current time: %s%n", t.now()));
});
frame.modulesPanel.add(button);
});
frame.pack();
frame.setVisible(true);
}

private final JPanel modulesPanel;
private final JLabel outputLabel;

public Main() {
super("Jigsaw Example");

modulesPanel = new JPanel();
modulesPanel.setLayout(new BoxLayout(modulesPanel, BoxLayout.LINE_AXIS));
add(modulesPanel, BorderLayoutNORTH);

outputLabel = new JLabel("output");
outputLabel.setHorizontalAlignment(SwingConstantsCENTER);
add(outputLabel, BorderLayout.CENTER);

setDefaultCloseOperation(EXIT_ON_CLOSE);
}
}

Add images to other modules and implement Image icon() method of modified TimeProvider. However, the modules are not yet dependent on java.desktop, so we can’t import class Image until we add a dependency in module-info. For this purpose there is a special syntax, allows to specify that the dependency should be automatically extended to other modules:

requires public java.desktop;

Then the module-info in main application will look like this:

// module-info.java
module com.example.timeapp {
requires java.base;
requires public java.desktop;

exports com.example.timeapp.spi;

uses com.example.timeapp.spi.TimeProvider;
}

Load image:

public class TimeLocalProvider implements TimeProvider {

private static Image icon;

@Override
public String now() {
return DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now());
}

@Override
public Image icon() {
if (icon == null) {
try (InputStream is = getClass().getResourceAsStream("/res/icon.png")) {
if (is != null)
icon = ImageIO.read(is);
} catch (IOException ignore) { }
}
return icon;
}
}

Similarly, edit the second module. It works!

Resources are loaded successfully

Example 3 on GitHub

Example 4. Automatic module

Well, what about earlier java libraries? We can’t add module-info to them. We have two choices:

  1. Add library to modulepath then it becomes an automatic module: its name will be the name of jar file, it will export all packages and read all open packages of other modules.
  2. Add library to classpath, then it becomes an unnamed module: we can’t add it as dependency and it will be able to read all package of modules.

For example, create a standard library without module-info and implement TimeProvider (don’t forget to add the main project to classpath):

// com/example/timemidnight/MidnightProvider.java
package com.example.timemidnight;
import com.example.timeapp.spi.TimeProvider;
import java.awt.Image;
import java.io.IOException;
import java.io.InputStream;
import javax.imageio.ImageIO;
public class MidnightProvider implements TimeProvider {

private static Image icon;

@Override
public String now() {
return "00:00";
}

@Override
public Image icon() {
if (icon == null) {
try (InputStream is = getClass().getResourceAsStream("/res/icon.png")) {
if (is != null)
icon = ImageIO.read(is);
} catch (IOException ignore) { }
}
return icon;
}
}

Compile, put jar to separate directory, call it midnight.jar and add to modulepath:

Dependencies in modulepath

Now we can add the automatic module midnight:

// module-info.java
module com.example.timeapp {
requires java.base;
requires public java.desktop;
requires midnight;

exports com.example.timeapp.spi;

uses com.example.timeapp.spi.TimeProvider;
}

MidnightProvider won’t appear in ServiceLoader, but we can create an instance of the class directly. Slightly rewrite the code in Main to be able to combine the providers of ServiceLoader with providers directly instantiated in the code:

Stream.concat(
StreamSupport.stream(serviceLoader.spliterator(), false),
Stream.of(new MidnightProvider())) // we can directly access class from automatic module
.forEach(t -> ...
Result

Example 4 on GitHub