Close

Java 9 Modules - Automatic Modules

[Updated: Aug 10, 2018, Created: Sep 30, 2017]

In this tutorial, we will understand what Automatic Modules are and how they can help us to use a non-modular third party library jar in our modular application.

What are Automatic Modules?

Automatic Modules are named modules which are automatically created for a non-modular jar. It happens when the jar is placed on the 'module path' (as dependency) of a modular application.

In other words; non modular jar files become modular (automatic module) when used by a modular application.

Let's say we have a modular application 'app' (has the module-info.class) which wants to use a third party non-modular jar 'lib.jar' (does not have the module-info.class). If we run a main class of 'app' say com.app.Main.class using following command:

java --module-path appClasses;lib --module app/com.app.Main

then lib.jar (placed under lib folder) automatically becomes modular for 'app'. This module has the same name as the jar name (extension is dropped, hyphens '-', if any, are replaced by dots '.' and version part, if any, is also dropped). The module 'app' has to still explicitly import the jar module by its name using 'requires' clause in module-info.java.

In above command, appClasses is the folder where 'app' has its complied classes and lib is the folder where we have the third party lib.jar.


Automatic Modules are another migration tool which helps us to develop a modular application or modularize our existing applications without waiting for the third party libraries to become modularized.

What packages it 'exports'?

It 'exports' all its packages.

What modules it 'requires'?

It 'requires' all other modules on the module path.

Example

This example will create two application. The first application will represent a non-modular third party library and the second one will be a modular application which will be using the first library .

The non-modular library

easy-math/src/easy/math/BasicCalc.java

package easy.math;
public class BasicCalc {
  public static int sum(int a, int b) {
      return a + b;
  }
}
D:\automatic-module-example\easy-math>tree /F /A
|
\---src
\---easy
\---math
BasicCalc.java

Compiling the class:

D:\automatic-module-example\easy-math>javac -d out src/easy/math/BasicCalc.java

Creating the jar:

D:\automatic-module-example>jar --create --file easy-math.jar -C easy-math/out .

The modular application

math.app/src/com/example/AppMain.java

package com.example;

import easy.math.BasicCalc;

public class AppMain {
  public static void main(String[] args) {
      int sum = BasicCalc.sum(4, 7);
      System.out.println("sum : " + sum);
  }
}

math.app/src/module-info.java

module math.app {
 requires easy.math;
}

Note that above module declaration requiring the dependency jar module (automatic) by it's name which will transform from 'easy-math.jar' to 'easy.math'

D:\automatic-module-example\math.app>tree /F /A
|
\---src
| module-info.java
|
\---com
\---example
AppMain.java

Moving easy-math.jar (from first project) to math.app application:

D:\automatic-module-example>mkdir math.app\lib
D:\automatic-module-example>move easy-math.jar math.app\lib\
1 file(s) moved.

Now here's our directory structure

D:\automatic-module-example>tree /F /A
|
+---easy-math
| |
| +---out
| | \---easy
| | \---math
| | BasicCalc.class
| |
| \---src
| \---easy
| \---math
| BasicCalc.java
|
\---math.app
|
+---lib
| easy-math.jar
|
\---src
| module-info.java
|
\---com
\---example
AppMain.java

Compiling the application:

D:\automatic-module-example\math.app>javac --module-path lib -d out src/module-info.java src/com/example/AppMain.java

Running the application

D:\automatic-module-example\math.app>java --module-path out;lib --module math.app/com.example.AppMain
sum : 11

Using jdeps

D:\automatic-module-example\math.app>jdeps --module-path lib;out -s --module math.app
math.app -> easy.math
math.app -> java.base

Using --describe-module

For the library jar:

D:\automatic-module-example\math.app>jar --file lib\easy-math.jar --describe-module
No module descriptor found. Derived automatic module.

easy.math automatic
requires java.base mandated
contains easy.math

For the client application:

D:\automatic-module-example\math.app>java --module-path out;lib\easy-math.jar --describe-module math.app
math.app file:///D:/automatic-module-example/math.app/out/
requires easy.math automatic
requires java.base mandated
contains com.example

Creating jar of the main application

Let's convert our main application to jar as well and try --describe-module on the jar:

D:\automatic-module-example\math.app>jar --create --file lib\math-app.jar -C out .
D:\automatic-module-example\math.app>jar --file lib\math-app.jar --describe-module
math.app jar:file:///D:/automatic-module-example/math.app/lib/math-app.jar/!module-info.class
requires easy.math
requires java.base mandated
contains com.example

Using Module API

Let's use new Module API to analyze the modules.

math.app/src/com/example/ModuleAnalysis.java

package com.example;

import easy.math.BasicCalc;

public class ModuleAnalysis {

  public static void main(String[] args) {
      analyze(ModuleAnalysis.class);
      System.out.println("------");
      analyze(BasicCalc.class);
  }

  private static void analyze(Class<?> cls) {
      System.out.println("class: " + cls);
      Module module = cls.getModule();
      System.out.println("Module: " + module);
      System.out.println("Name: " + module.getName());
      System.out.println("isNamed: " + module.isNamed());
      System.out.println("Descriptor: " + module.getDescriptor());
      System.out.println("isAutomatic: " + module.getDescriptor().isAutomatic());
  }
}

We just need to compile above class without recompiling the rest:

D:\automatic-module-example\math.app>javac --module-path lib -d out src/com/example/ModuleAnalysis.java
D:\automatic-module-example\math.app>java --module-path out;lib --module math.app/com.example.ModuleAnalysis
class: class com.example.ModuleAnalysis
Module: module math.app
Name: math.app
isNamed: true
Descriptor: module { name: math.app, [easy.math, mandated java.base (@9)] }
isAutomatic: false
------
class: class easy.math.BasicCalc
Module: module easy.math
Name: easy.math
isNamed: true
Descriptor: module { name: easy.math, [mandated java.base] }
isAutomatic: true

How automatic module name is derived?

From ModuleFinder doc:

A JAR file that does not have a module-info.class in its top-level directory defines an automatic module, as follows:
  • If the JAR file has the attribute "Automatic-Module-Name" in its main manifest then its value is the module name. The module name is otherwise derived from the name of the JAR file.
  • The version, and the module name when the attribute "Automatic-Module-Name" is not present, are derived from the file name of the JAR file as follows:
    • The ".jar" suffix is removed.
    • If the name matches the regular expression "-(\\d+(\\.|$))" then the module name will be derived from the subsequence preceding the hyphen of the first occurrence. The subsequence after the hyphen is parsed as a Version and ignored if it cannot be parsed as a Version.
    • All non-alphanumeric characters ([^A-Za-z0-9]) in the module name are replaced with a dot ("."), all repeating dots are replaced with one dot, and all leading and trailing dots are removed.
    • As an example, a JAR file named "foo-bar.jar" will derive a module name "foo.bar" and no version. A JAR file named "foo-bar-1.2.3-SNAPSHOT.jar" will derive a module name "foo.bar" and "1.2.3-SNAPSHOT" as the version.

Example Project

Dependencies and Technologies Used:

  • JDK 9
Java 9 Automatic Example Select All Download
  • automatic-module-example
    • easy-math
      • src
        • easy
          • math
    • math.app
      • src
        • com
          • example
            • AppMain.java

    See Also