Java 9 Modules
Java Platform Module System
What are Modules
Modules are a new way of keeping code and data together in Java. It is a set of packages which includes classes and interfaces along with their resources such as property files and other static data. A module can require other modules (i.e.) it can explicitly specify that it depends on other modules. It can export packages too making them available for other modules to use. A JAR can become a module, if the module-info.class
is added. This module-info.class
is the compiled version of module-info.java. module-info.java is the file where the details such as a module’s name, it’s dependencies and the packages it exports are defined.
Why modules are needed
Some of the problems that arise from the existing architecture of Java are,
- Unexpressed dependencies - A JAR file can not express which other JARs it depends on with out the help of build systems such as maven. A JAR named SPRING-DATA can not express that it depends on SPRING-CORE unless a build system like maven is used. Developers would have to explicitly download the required JAR files and add them to the project. If some dependencies are not added, that will cause NoClassDefFoundError.
- JAR hell - This happens when two files of the same name are needed in a project. These two files could belong to different libraries or even different versions of the same library. The Java class loader will load only one of them and will ignore the other thinkig that it is loaded already. This can cause serious problems and hard to track down errors. When multiple libraries(JARS) are loaded for a project in a classpath their namespace become linear or flat. If there is a class named
com.hello.world.Greet
in the libraryHelloWorld
and another class with the same namecom.hello.world.Greet
in the libraryGreeting
, the classloader can not differentiate between them. You might think that both classes are in different JAR files and in different libraries, but the namespace becomes one when they are loaded. Sometimes a missing class is simply not found even till the runtime. - Fat JAR - JDK is really big. It is over 100 MB. Even for something as simple as “Hello world”, it initializes over 300 classes. This also has effect upon the start up time. How many times have you tried to import java.util.List in Eclipse, but always have to choose between that and java.awt.List ? Unless you are an UI developer you do not need the java.awt.List at all. But that is also present in the Fat JAR which consists of all the packages in Java. This causes serious problems in small devices where the memory is less. Even in servers, these unnecessary memory spaces of JARs add up when deploying 100s of instances of virtual servers.
Benefits of using modules
- Finding out the missing modules - An application module specifies which other modules(3rd party modules or Java API modules) it depends on. This helps the JVM to find out the missing modules if any and shutdown the application. This makes applications robust. No more unexpressed dependencies.
- Better encapsulation - A Java module has to explicitly mention which packages it exports. Before Java 9, all the public packages are open. If one package is needed for another package inside the application, it has to be made public, which inturn makes that package visible for all the classes in other applications too. In Java 9 we can specify which packages we want to export. A public package is simply not visible outside an application by default unless we explicitly export that.
- Smaller distributions - Before Java 9 it was difficult to check the classes that a Java application uses. Because of that, a distribution has to package all the classes, making an application bloated. It uses unnecessary memory. The work undertaken as part of the Project Jigsaw, has split all the Java platform APIs into separate modules. Using Java 9, we can explicitly state which modules we use and only those modules get packaged. This reduces the memory usage and size of applications. This is a soluiton to the fat JAR problem of Java.
Modules in action
Example 1
Let us create a module named com.hello
that prints “Hello world”. This module has two files. The module-info.java
(module declaration or module descriptor file) and the Main.java
.
The project structure
The main class - src/com.hello/com/hello/Main.java
package com.hello;
public class Main{
public static void main(String...args){
System.out.println("Hello World");
}
}
The module descriptor - src/com.hello/module-info.java
module com.hello{}
All the files are stored under a folder named hello-module and in the same directory a folder named mods is created to store the compiled files of the com.hello module.
$ mkdir -p mods/com.hello
$ ls
hello-module mods
Compile and execute the sources
$ javac -d mods/com.hello hello-module/src/com.hello/module-info.java hello-module/src/com.hello/com/hello/Main.java
$ java ---module-path mods -m com.hello/com.hello.Main
Hello World
The statement -d mods/com.hello
asks the java compiler to store the compiled source files in the mods/com.hello directory.
The –module-path, as the name suggests sets the path where the modules are stored. The -m option specifies the main module and main class in that module. In our example, the main module is com.hello and the main class under that is Main.java, after -m, we mention com.hello/com.hello.Main.
Let us look at another example. This time we will see how to export a package and declare a dependency on an another module.
Example 2
- We are going to create two modules,
com.hello
andcom.info
. - com.info will export the package
com.info.data
. - com.hello will require the
com.info
module.
The project structure and content of the com.info module
info-module/src/com.info/module-info.java
info-module/src/com.info/com/info/data/Information.java
The Information.java
has one method named getInformation
which simply returns the current date and time.
package com.info.data;
import java.time.LocalDateTime;
public class Information{
public String getInformation(){
return "The time now is "+LocalDateTime.now().toString();
}
}
The module-info.java
exports the package com.info.data
.
module com.info {
exports com.info.data;
}
The project structure of com.hello module
hello-module/src/com.hello/module-info.java
hello-module/src/com.hello/com/hello/Main.java
The module-info.java
“requires” the com.info
module. It means that it depends on the com.info module.
module com.hello {
requires com.info;
}
The main class makes use of the com.info.data.Information.java
class
package com.hello;
import com.info.data.Information;
public class Main{
public static void main(String...args){
Information info = new Information();
System.out.println("Hello world");
System.out.println(info.getInformation());
}
}
The com.hello module’s files are present inside the hello-module folder and com.info module’s files are present inside the info-module folder.
Compile the com.info module
javac -d mods/com.info info-module/src/com.info/module-info.java info-module/src/com.info/com/info/data/Information.java
The -d mods/com.info
sets the path for the compiled class files of the com.info module to be stored.
Compile the com.hello module
javac --module-path mods -d mods/com.hello hello-module/src/com.hello/module-info.java hello-module/src/com.hello/com/hello/ Main.java
- –module-path : This sets the folder location where all the modules, especially the modules that com.hello depends on, are present. In the command above it is the mods folder.
- -d : Sets the folder where this module is compiled and stored. In the command above it is mods/com.hello folder
If we look at the mods folder it will contain com.hello and com.info folders, which in turn will contain com.hello and com.info modules respectively.
$ java --module-path mods -m com.hello/com.hello.Main Hello world The time now is 2018-08-05T09:24:25.085475
When executing the
--module-path
sets the location of the modules. This is like setting the classpath. The option-m
sets the main module information. It’s syntax is mainModule/mainClassOfTheModule. In the example above the main module iscom.hello
and the main class iscom.hello.Main
.Modules must be given a unique name. A Java module follows the same convention as Java packages.
Modules must be nested under a root directory with the same name as the module. In the example above, the module
com.hello
is under the directorycom.hello
. The file structure is like this,src/com.hello/com/hello
. “com.hello” is the root folder here with module.info under it.src/com.hello/module-info.java
. Under the root folder, the module-info.java must be added. Another example,src/main/java/com.framework/module-info.java
. If there are two modules in the project, then,src/main/java/com.framework1/module-info.java
andsrc/main/java/com.framework2/module-info.java
.A module must explicitly export the packages that are to be accessible for other modules using the module. In the example above about the
com.info
module, it exportscom.info.data
package explicitly. Otherwise, it will not be visible to any other modules. Also if there are any sub-packages or parent packages, they are not exported automatically. If there is a sub-package for com.info.data named util, that is not exported when the com.info.data is exported. Also the parent package com.info is also not exported automatically.If a module is dependent on another module, the other module must be specified in the module-info.java with the require keyword.
com.hello
module above was dependent oncom.info
, hence it specified it explicitly by doingrequire com.info
.Circular dependencies like A requires B and B requires A is not allowed.
Some examples of the Java API’s modules are java.net.jmod, java.naming.jmod, java.base.jmod, java.se.ee.jmod. The java.base module is added to all the modules by default.
Conclusion
The ability to support modules from Java 9 will definitely make applications more robust, faster and extremely reliable. The new syntax might take a while for us to understand, but the benefits of using the modules in the new projects are truly beneficial.