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


Index Page : Link

Donate : Link

Medium Blog : Link

Applications : Link

Introduction:

Modularity concept introduced in Java 9 as the part of Jigsaw project. It is the main important concept in Java 9.

The development of modularity concept started in 2005. The First JEP (JDK Enhancement Proposal) for Modularity released in 2005. Java people tried to release Modularity concept in Java 7 (2011) & Java 8 (2014). But they failed. Finally after several postponements this concept introduced in Java 9.

Until Java 1.8 version we can develop applications by writing several classes, interfaces and enums. We can places these components inside packages and we can convert these packages into jar files. By placing these jar files in the classpath, we can run our applications. An enterprise application can contain 1000s of jar files also.

Hence jar file is nothing but a group of packages and each package contains several .class files.

But in Java 9, a new construct got introduced which is nothing but ‘Module’. From java 9 version onwards we can develop applications by using module concept.

Module is nothing but a group of packages similar to jar file. But the specialty of module when compared with jar file is, module can contain configuration information also.

Hence module is more powerful than jar file. The configuration information of module should be specified in a special file named with module-info.java

Every module should compulsory contains module-info.java, otherwise JVM won’t consider that as a module of Java 9 platform.

In Java 9, JDK itself modularized. All classes of Java 9 are grouped into several modules (around 98) like
java.base
java.logging
java.sql
java.desktop(AWT/Swing)
java.rmi etc

java.base module acts as base for all java 9 modules.

We can find module of a class by using getModule() method.

E.g. System.out.println(String.class.getModule()); //module java.base

What is the need of JPMS?

Application development by using jar file concept has several serious problems.

Problem-1: Unexpected NoClassDefFoundError in middle of program execution

There is no way to specify jar file dependencies until java 1.8V. At runtime, if any dependent jar file is missing then in the middle of execution of our program, we will get NoClassDefFoundError, which is not at all recommended.

Demo Program to demonstrate NoClassDefFoundError:

A1.java

package pack1;

public class A1
{
  public void m1()
  {
    System.out.println("pack1.A1");
  }
}

A2.java

package pack2;
import pack1.A1;

public class A2
{
  public void m2()
  {
    System.out.println("pack2.A2");
    A1 a = new A1();
    a.m1();
  }
}

Test.java

import pack2.A2;

class Test
{
  public static void main(String... args)
  {
    System.out.println("main class");
    A2 a = new A2();
    a.m2();
  }
}
CodeFactory
--Test.class
--pack1
----A1.class
--pack2
----A2.class

D:\CodeFactory> javac -d . A1.java
D:\CodeFactory> javac -d . A2.java
D:\CodeFactory> javac Test.java

At runtime, by mistake if pack1 is not available then after executing some part of the code in the middle, we will get NoClassDefFoundError.

D:\CodeFactory> java Test
main class
pack2.A2
Exception in thread "main" java.lang.NoClassDefFoundError: pack1/A1

But in Java9, there is a way to specify all dependent modules information in module-info.java. If any module is missing then at the beginning only, JVM will identify and won’t start its execution. Hence there is no chance of raising NoClassDefFoundError in the middle of execution.

Problem 2: Version Conflicts or Shadowing Problems

If JVM required any .class file, then it always searches in the classpath from left to right until required match found.

classpath = jar1;jar2;jar3;jar4;jar5

If jar4 requires Test.class file of jar3.But Different versions of Test.class is available in jar1, jar2 and jar3. In this case jar1 Test.class file will be considered, because JVM will always search from Left to Right in the classpath. It will create version conflicts and causes abnormal behavior of program.

But in java9 module system, there is a way to specify dependent modules information for every module seperately.JVM will always consider only required module and there is no order importance. Hence version conflicts won’t be raised in Java 9.

Problem 3: Security problem

There is no mechanism to hide packages of jar file.

Assume pack1 can be used by other jar files, but pack2 is just for internal purpose only. Until Java 8 there is no way to specify this information. Everything in jar file is public and available to everyone. Hence there may be a chance of Security problems.

public is too much public in jar files.

But in Java 9 Module system, we can export particular package of a module. Only this exported package can be used by other modules. The remaining packages of that module are not visible to outside. Hence Strong encapsulation is available in Java 9 and there is no chance of security problems.

Even though class is public, if module won’t export the corresponding package, then it cannot be accessed by other modules. Hence public is not really that much public in Java 9 Module System.

Module can offer Strong Encapsulation than Jar File.

Problem 4: JDK/JRE having Monolithic Structure and Very Large Size

The number of classes in Java is increasing very rapidly from version to version.

JDK 1.0V having 250+ classes
JDK 1.1V having 500+ classes
…..
JDK 1.8V having 4000+ classes

And all these classes are available in rt.jar.
Hence the size of rt.jar is increasing from version to version.
The size of rt.jar in Java 1.8Version is around 60 MB.

To run small program also, total rt.jar should be loaded, which makes our application heavy weight and not suitable for IOT applications and micro services which are targeted for portable devices.

It will create memory and performance problems also. (This is something like inviting a Big Elephant in our Small House: Installing a Heavy Weight Java application in a small portable device).

But in java 9, rt.jar removed. Instead of rt.jar all classes are maintained in the form of modules. Hence from Java 9 onwards JDK itself modularized. Whenever we are executing a program only required modules will be loaded instead of loading all modules, which makes our application light weighted.

Now we can use java applications for small devices also. From Java 9 version onwards, by using JLINK , we can create our own very small custom JREs with only required modules.

Explain differences between jar file and java 9 module

JarModule
Jar is a group of packages and each package contains several classesModule is also a group of packages and each package contains several classes. Module can also contain one special file module-info.java to hold module specific dependencies and configuration information.
In jar file, there is no way to specify dependent jar files informationFor every module we have to maintain a special file module-info.java to specify module dependencies
There is no way to check all jar file dependencies at the beginning only. Hence in the middle of the program execution there may be a chance of NoClassDefFoundError.JVM will check all module dependencies at the beginning only with the help of moduleinfo.java. If any dependent module is missing then JVM won’t start its execution. Hence there is no chance of NoClassDefFoundError in the middle of execution.
In the classpath the order of jar files important and JVM will always considers from left to right for the required .class files. If multiple jars contain the same .class file then there may be a chance of Version conflicts and results abnormal behavior of our applicationIn the module-path order is not important. JVM will always check from the dependent module only for the required .class files. Hence there is no chance of version conflicts and abnormal behavior of the application.
In jar file there is no mechanism to control access to the packages. Everything present in the jar file is public to everyone. Any person is allowed to access any component from the jar file. Hence there may be a chance of security problemsIn module there is a mechanism to control access to the packages. Only exported packages are visible to other modules. Hence there is no chance of security problems
Jars follows monolithic structure and applications will become heavy weight and not suitable for small devices.Modules follow distributed structure and applications will become light weighted and suitable for small devices.
Jar files approach cannot be used for IOT devices and micro services.Modules based approach can be used for IOT devices and micro services

What is Jar Hell or Classpath Hell?

The problems with use of jar files are:

  1. NoClassDefFoundError in the middle of program execution
  2. Version Conflicts and Abnormal behavior of program
  3. Lack of Security
  4. Bigger Size

This set of Problems is called Jar Hell OR Classpath Hell. To overcome this, we should go for JPMS.

What are various Goals/Benefits of JPMS?

1. Reliable Configuration
2. Strong Encapsulation & Security
3. Scalable Java Platform
4. Performance and Memory Improvements
etc…

What is a Module:

Module is nothing but collection of packages. Each module should compulsory contains a special configuration file: module-info.java.

We can define module dependencies inside module-info.java file.

module moduleName
{
  Here we have to define module dependencies which represents
  1. What other modules required by this module?
  2. What packages exported by this module for other modules?
  etc
}

Steps to Develop First Module Based Application:

Step-1: Create a package with our required classes

package pack1;
public class Test
{
  public static void main (String... args)
  {
    System.out.println("Module in JPMS");
  }
}

Step-2: Writing module-info.java

For every module we should to write a special file named with module-info.java. In this file we have to define dependencies of module.

module moduleA
{
}

Step-3: Compile module with –module-source-path option

D:\CodeFactory>javac --module-source-path src -d out -m moduleA

The generated class file structure is:

Step-4: Run the class with –module-path option

D:\CodeFactory>java --module-path out -m moduleA/pack1.Test

Output: Module in JPMS

Case-1:

If module-info.java is not available then the code won’t compile and we will get error. Hence module-info.java is mandatory for every module.

javac –module-source-path src -d out -m moduleA
error: module moduleA not found in module source path

Case-2:

Every class inside module should be part of some package, otherwise we will get compile time
error saying : unnamed package is not allowed in named modules
In the above application inside Test.java if we comment package statement

// package pack1;

public class Test
{
  public static void main (String... args)
  {
    System.out.println("Module in JPMS");
  }
}

error: unnamed package is not allowed in named modules

Case-3:

The module name should not ends with digit(like module1,module2 etc), otherwise we will get warning at compile time.

javac –module-source-path src -d out -m module1
warning: [module] module name component module1 should avoid terminal digits

Various Possible Ways to Compile a Module:

javac --module-source-path src -d out -m moduleA
javac --module-source-path src -d out --module moduleA
javac --module-source-path src -d out
 src/moduleA/module-info.java src/moduleA/pack1/Test.java
javac --module-source-path src -d out
 
  D:/New/CodeFactory/src/moduleA/module-info.java
 
  D:/New/CodeFactory/src/moduleA/pack1/Test.java

Various Possible Ways to Run a Module:

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

Inter Module Dependencies:

Within the application we can create any number of modules and one module can use other modules.

We can define module dependencies inside module-info.java file.

module moduleName
{
  Here we have to define module dependencies which represents
  1. What other modules required by this module?
  2. What packages exported by this module for other modules?
  etc
}

Mainly we can use the following 2 types of directives

1. requires directive:

It can be used to specify the modules which are required by current module.

E.g:

module moduleA
{
  requires moduleB;
}

It indicates that moduleA requires members of moduleB.

Note:

  1. We cannot use same requires directive for multiple modules. For every module we have to use separate requires directive.
    requires moduleA,moduleB; -> invalid
  2. We can use requires directive only for modules but not for packages and classes.

2. exports directive:

It can be used to specify what packages exported by current module to the other modules.

E.g.

module moduleA
{
  exports pack1;
}

It indicates that moduleA exporting pack1 package so that this package can be used by other modules.

Note: We cannot use same exports directive for exporting multiple packages. For every package a separate exports directive must be required.
exports pack1,pack2; -> invalid

Note: Be careful about syntax requires directive always expecting module name where as exports directive expecting package name.

module module_name
{
  requires module_name;
  exports package_name;
}

Note: By default all packages present in a module are private to that module. If module exports any package only that particular package is accessible by other modules. Non exporting packages cannot be accessed by other modules.
E.g. Assume moduleA contains 2 packages pack1 and pack2. If moduleA exports only pack1 then other modules can use only pack1. pack2 is just for its internal purpose and cannot be accessed by other modules.

module moduleA
{
  exports pack1;
}

Demo program for inter module dependencies:

moduleA components:

A.java

package pack1;

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

module-info.java

module moduleA
{
  exports pack1;
}

moduleB components:

Test.java

package pack2;
import pack1.A;

public class Test
{
  public static void main (String... args)
  {
    System.out.println("moduleB accessing members of moduleA");
    A a = new A();
    a.m1();
  }
}

module-info.java:

module moduleB
{
  requires moduleA;
}

Compilation:

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

Note: space is not allowed between the module names otherwise we will get error.

D:\CodeFactory>javac --module-source-path src -d out -m moduleA, moduleB
error: Class names, 'moduleB', are only accepted if annotation processing is explicitly requested
1 error

Execution:

D:\CodeFactory>java --module-path out -m moduleB/pack2.Test
moduleB accessing members of moduleA
Module m1() of moduleA

Case-1:

Even though class A is public, if moduleA won’t export pack1, then moduleB cannot access A class.

E.g.

module moduleA
{
  // exports pack1;
}
D:\CodeFactory>javac --module-source-path src -d out -m moduleA,moduleB

Note: if out folder is already present then it will not give anyb error, need to remove it and then compile

D:\CodeFactory>javac --module-source-path src -d out -m moduleA,moduleB
src\moduleB\pack2\Test.java:2: error: package pack1 is not visible
import pack1.A;
       ^
  (package pack1 is declared in module moduleA, which does not export it)
1 error

Case-2:

We have to export only packages. If we are trying to export modules or classes then we will get compile time error.

Eg-1: exporting module instead of package

module moduleA
{
  exports moduleA;
}
D:\CodeFactory>javac --module-source-path src -d out -m moduleA,moduleB
src\moduleB\pack2\Test.java:2: error: package pack1 is not visible
import pack1.A;
       ^
  (package pack1 is declared in module moduleA, which does not export it)
src\moduleA\module-info.java:3: error: package is empty or does not exist: moduleA
  exports moduleA;
          ^
2 errors

In this case compiler considers moduleA as package and it is trying to search for that package.

Eg-2: exporting class instead of package:

module moduleA
{
  exports pack1.A;
}
D:\CodeFactory>javac --module-source-path src -d out -m moduleA,moduleB
src\moduleB\pack2\Test.java:2: error: package pack1 is not visible
import pack1.A;
       ^
  (package pack1 is declared in module moduleA, which does not export it)
src\moduleA\module-info.java:3: error: package is empty or does not exist: pack1.A
  exports pack1.A;
               ^
2 errors

In this case compiler considers pack1.A as package and it is trying to search for that package.

Case-3:

If moduleB won’t use “requires moduleA” directive then moduleB is not allowed to use members of moduleA, even though moduleA exports.

module moduleB
{
  // requires moduleA;
}
D:\CodeFactory>javac --module-source-path src -d out -m moduleA,moduleB
src\moduleB\pack2\Test.java:2: error: package pack1 is not visible
import pack1.A;
       ^
  (package pack1 is declared in module moduleA, but module moduleB does not read it)
1 error

Case-4:

If compiled codes are available in different packages then how to run?
We have to use special option: –upgrade-module-path

If compiled codes of moduleA is available in out and compiled codes of moduleB available in out2

D:\CodeFactory>javac --module-source-path src -d out -m moduleA

D:\CodeFactory>javac --module-source-path src -d out2 -m moduleB

D:\CodeFactory>java --upgrade-module-path out;out2 -m moduleB/pack2.Test
moduleB accessing members of moduleA
Module m1() of moduleA

Case-5:

If source codes of two modules are in different directories then how to compile?

Assume moduleA source code is available in src directory and moduleB source code is available in src2

D:\CodeFactory>javac --module-source-path src;src2 -d out -m moduleA, moduleB

Which of the following are meaningful?

module moduleName
{
  1. requires modulename;
  2. requires modulename.packagename;
  3. requires modulename.packagename.classname;
  4. exports modulename;
  5. exports packagename;
  6. exports packagename.classname;
}

Answer: 1 & 5 are Valid

Note: We can use exports directive only for packages but not modules and classes, and we can use requires directive only for modules but not for packages and classes.

Note: To access members of one module in other module, compulsory we have to take care the following 3 things.

  1. The module which is accessing must have requires dependency
  2. The module which is providing functionality must have exports dependency
  3. The member must be public.

Leave a comment