Java 9 – The Java Platform Module System (JPMS) Part 3 | Code Factory


Index Page : Link

Donate : Link

Medium Blog : Link

Applications : Link

Module Graph:

The dependencies between the modules can be represented by using a special graph, which is nothing but Module Graph.

Eg1: If moduleA requires moduleB then the corresponding module graph is

module moduleA
{
  requires moduleB;
}

Eg2: If moduleA requires moduleB and moduleC then the corresponding module graph is

module moduleA
{
  requires moduleB;
  requires moduleC;
}

Eg3: If moduleA requires moduleB and moduleB requires moduleC then the corresponding module graph is

module moduleA
{
  requires moduleB;
}

module moduleB
{
  requires moduleC;
}

Eg4: If moduleA requires moduleB and moduleB requires transitive moduleC then the corresponding module graph is

module moduleA
{
  requires moduleB;
}

module moduleB
{
  requires transitic moduleC;
}

Eg5: If moduleA requires moduleB and moduleC, moduleC requires moduleD and transitive moduleE then the corresponding Modular Graph is

module moduleA
{
  requires moduleB;
  requires moduleC;
}

module moduleC
{
  requires moduleD;
  requires transitive moduleE;
}

Java 9 JDK itself modularized. All classes of Java SE are divided into several modules.

Eg:

java.base
java.sql
java.xml
java.rmi
etc…

The module graph of JDK is

In the above diagram all modules requires java.base module either directly or indirectly. Hence this module acts as BASE module for all java modules.

Observe modular graphs carefully:java.se and java.sql modules etc

Rules of Module Graph:

  1. Two modules with the same name is not allowed.
  2. Cyclic Dependency is not allowed between the modules and hence Module Graph should not contain cycles.

Observable Modules:

The modules which are observed by JVM at runtime are called Observable modules.

The modules we are specifying with –module-path option with java command are observed by JVM and hence these are observable modules.

java --module-path out -m moduleA/pack1.Test

The modules present in module-path out are observable modules

JDK itself contains several modules (like java.base, java.sql, java.rmi etc). These modules can be observed automatically by JVM at runtime and we are not required to use –module-path. Hence these are observable modules.

Observable Modules = All Predefined JDK Modules + The modules specified with –module-path option

We can list out all Observable Modules by using --list-modules option with java command.

Eg1: To print all readymade compiled modules (pre defined modules) present in Java 9

D:\CodeFactory>java --list-modules
java.base@11.0.10
java.compiler@11.0.10
java.datatransfer@11.0.10
java.desktop@11.0.10
java.instrument@11.0.10
...
...

Eg2: Assume our own created compiled modules are available in out folder. To list out these modules including readymade java modules

D:\CodeFactory>java --module-path out --list-modules
java.base@11.0.10
java.compiler@11.0.10
java.datatransfer@11.0.10
java.desktop@11.0.10
...
...
exportermodule file:///D:/CodeFactory/out/exportermodule/
moduleA file:///D:/CodeFactory/out/moduleA/
moduleB file:///D:/CodeFactory/out/moduleB/

Aggregator Module:

Sometimes a group of modules can be reused by multiple other modules. Then it is not recommended to read each module individually. We can group those common modules into a single module, and we can read that module directly. This module which aggregates functionality of several modules into a single module is called Aggregator module. If any module reads aggregator module then automatically all its modules are by default available to that module.

Aggregator module won’t provide any functionality by its own, just it gathers and bundles together a bunch of other modules.

module aggregateModule
{
  requires transitive moduleA;
  requires transitive moduleB;
  requires transitive moduleC;
}

Aggregator Module not required to contain a single java class. Just it “requires transitive” of all common modules.

If any module reads aggregatorModule automatically all 3 modules are by default available to that module also.

module useModule
{
  requires aggregateModule;
}

Now useModule can use functionality of all 3 modules moduleA, moduleB and moduleC.

Demo Program for Aggregator Module:

moduleA components:

A.java

package pack1;

public class A
{
  public void m1()
  {
    System.out.println("moduleA method m1()");
  }
}

module-info.java

module moduleA
{
  exports pack1;
}

moduleB components:

B.java

package pack2;

public class B
{
  public void m1()
  {
    System.out.println("moduleB method m1()");
  }
}

module-info.java

module moduleB
{
  exports pack2;
}

moduleC components:

C.java

package pack3;

public class C
{
  public void m1()
  {
    System.out.println("moduleC method m1()");
  }
}

module-info.java

module moduleC
{
  exports pack3;
}

aggregatorModule components:

module-info.java

module aggregatorModule
{
  requires transitive moduleA;
  requires transitive moduleB;
  requires transitive moduleC;
}

useModule components:

Test.java

package packA;
import pack1.A;
import pack2.B;
import pack3.C;

public class Test
{
  public static void main(String... args)
  {
    System.out.println("Aggregator module demo");
    A a = new A();
    a.m1()
    
    B b = new B();
    b.m1()    
    
    C c = new C();
    c.m1()
  } 
}

module-info.java

Here we are not required to use requires directive for every module, just we have to use requires only for aggregatorModule.

module userModule
{
  // requires moduleA;
  // requires moduleB;
  // requires moduleC;
  requires aggregatorModule;
}
D:\CodeFactory>javac --module-source-path src -d out -m moduleA,moduleB,moduleC,aggregatorModule,useModule

D:\CodeFactory>java --module-path out -m useModule/packA.Test
Aggregator module demo
moduleA method m1()
moduleB method m1()
moduleC method m1()

Package Naming Conflicts:

Two jar files can contain a package with same name, which may creates version conflicts and abnormal behavior of the program at runtime.

But in Java 9 module System, two modules cannot contain a package with same name; otherwise we will get compile time error. Hence in module system, there is no chance of version conflicts and abnormal behavior of the program.

Demo Program:

moduleA components:

A.java

package pack1;

public class A
{
  public void m1()
  {
    System.out.println("moduleA method m1()");
  }
}

module-info.java

module moduleA
{
  exports pack1;
}

moduleB components:

B.java

package pack1;

public class B
{
  public void m1()
  {
    System.out.println("moduleB method m1()");
  }
}

module-info.java

module moduleB
{
  exports pack1;
}

useModule components:

Test.java

package packA;

public class Test
{
  public static void main(String... args)
  {
    System.out.println("Package naming conflicts");
  } 
}

module-info.java

module useModule
{
  requires moduleA;
  requires moduleB;
}
D:\CodeFactory>javac --module-source-path src -d out -m moduleA,moduleB,useModule
src\useModule\module-info.java:1: error: module useModule reads package pack1 from both moduleA and moduleB
module useModule
^
1 error

Two modules cannot contain a package with same name.

Module Resolution Process (MRP):

In the case of traditional classpath, JVM won’t check the required .class files at the beginning. While executing program if JVM required any .class file, then only JVM will search in the classpath for the required .class file. If it is available then it will be loaded and used and if it is not available then at runtime we will get NoClassDefFoundError,which is not at all recommended.

But in module programming, JVM will search for the required modules in the module-path before it starts execution. If any module is missing at the beginning only JVM will identify and won’t start its execution. Hence in modular programming, there is no chance of getting NoClassDefFoundError in the middle of program execution.

Demo Program:

Components of useModule:

Test.java

package packA;

public class Test
{
  public static void main(String... args)
  {
    System.out.println("Module Resolution Process(MRP) Demo");
  } 
}

module-info.java

module useModule
{
  requires moduleA;
}

Components of moduleA:

module-info.java

module moduleA
{
  requires moduleB;
}

Components of moduleB:

module-info.java

module moduleB
{
  requires moduleC;
  requires moduleD;
}

Components of moduleC:

module-info.java

module moduleC
{

}

Components of moduleD:

module-info.java

module moduleD
{

}
D:\CodeFactory>javac --module-source-path src -d out -m moduleA,moduleB,moduleC,moduleD,useModule

D:\CodeFactory>java --module-path out --show-module-resolution -m useModule/packA.Test
root useModule file:///D:/CodeFactory/out/useModule/
useModule requires moduleA file:///D:/CodeFactory/out/moduleA/
moduleA requires moduleB file:///D:/CodeFactory/out/moduleB/
moduleB requires moduleC file:///D:/CodeFactory/out/moduleC/
moduleB requires moduleD file:///D:/CodeFactory/out/moduleD/
java.base binds jdk.localedata jrt:/jdk.localedata
java.base binds jdk.jlink jrt:/jdk.jlink
java.base binds jdk.compiler jrt:/jdk.compiler
...
...
jdk.naming.dns requires java.naming jrt:/java.naming
java.rmi requires java.logging jrt:/java.logging
Module Resolution Process(MRP) Demo

The module what we are trying to execute will become root module.
Root module should contain the class with main method.

The main advantages of Module Resolution Process at beginning are:

  1. We will get error if any dependent module is not available.
  2. We will get error if multiple modules with the same name.
  3. We will get error if any cyclic dependency.
  4. We will get error if two modules contain packages with the same name.

Note:

The following are restricted keywords in java 9:

module,requires,transitive,exports

In normal java program no restrictions and we can use for identifier purpose also.

Leave a comment