Introduction
The idea and exploration of Project Jigsaw began in 2008 with the aim to split JDK into smaller parts. Work on the design and implementation for Java 9 began in 2014 and was released as part of JDK 9 in September 2017.
The major purposes of JPMS are the following.
- A format for module JAR files
- Partitioning of the JDK into modules.
- Additional command line options
A module is a group of one or more packages plus a special file named module-info.java.
Benefits of JPMS
- Fifth Access level
- Before Java9, there were four access levels i.e private, package, protect, and public. Fifth access level is to restrict Class to particular package/ module on top of others.
- Clearer Dependency Management
- Each of the compile time or runtime dependency has to be declared in module-info.java file.
- Reduced memory requirement
- Installing JRE would take 120MB, however with JPMS tools like jlink, we can specify which modules we need to create smaller custome runtime images that can run without having Java installed.
- Better Performance
- Since java knows which modules are required, it only need to look at those classes at loading time. This improves startup time for big programs.
Create Modular Applications
In this section, we will create and run three applications using modules.
1-Create a Vehicle application
From Eclipse, create a new project with Java 11, project name com.jones.moduledemo.vehicle.
Step-1:Create module-info.java
module com.jones.moduledemo.vehicle {
exports com.jones.moduledemo.vehicle;
}
Step-2:Create a Vehicle class
package com.jones.moduledemo.vehicle;
public class Vehicle {
public static void main(String[] args) {
System.out.println("From The Vehicle class");
}
}
Step-3:Eclipse workspace : Output
Step-4:Export Module
In Eclipse, export the project and create a module jar
2-Create a Car application
Create a Car Application, and add the com.jones.moduledemo.vehicle module to module-path.
Step-1:Create module-info.java
module com.jones.moduledemo.cars {
exports com.jones.moduledemo.cars;
requires transitive com.jones.moduledemo.vehicle;
}
Transitive: The module-info.java file needs com.jones.moduledemo.vehicle as a transitive dependency, which means any module that adds com.jones.moduledemo.cars module need not separately add vehicle module into their module-info.java file.
Cyclic dependency: JPMS does not allow cyclic dependencies. A cyclic dependency is when two requires modules are directly or indirectly depend on each other.
Step-2:Add com.jones.moduledemo.vehicle to modulepath
RightClick on Project -> BuildPath -> ConfigureBuildPath -> Libraries -> Add ExternalJar
Step-3:Create a Car Class
package com.jones.moduledemo.cars;
public class Car {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
System.out.println(" Inside cars");
}
}
Step-4:Eclipse workspace : Output
3-Create a Driver application
Step-1:Create module-info.java file
module com.jones.moduledemo.driver {
exports com.jones.moduledemo.driver;
requires com.jones.moduledemo.cars;
}
Import cars, and Vehicle modules into the application module path
Step-2:Create the Driver Class
We will use the Vehicle, Car classes in Driver module for showing java9 modular features.
package com.jones.moduledemo.driver;
import com.jones.moduledemo.cars.Car;
import com.jones.moduledemo.vehicle.Vehicle;
public class Driver {
public static void main(String[] args) {
Vehicle vehicle = new Vehicle();
Car car = new Car();
System.out.println("Inside Driver");
}
}
Output
Additional command line options
We are able to use both Vehicles, and Car in the driver application using module applications from IDE like Eclipse, now let us see different command-line options given in Java9.
Description | Command |
Compile module | javac -p moduleFolder -d outputDirectory $(find ./ -name “*java”) |
Run module | java -p moduleFolder -m moduleName/package.className |
Create module jar | jar -cvf moduleFolder/modulename.jar -C path/ . |
Describe module (Lists the exported modules, List dependencies, Lists exports with to where List unexported package) | java -p moduleFolder -d moduleName jar -f jarName -d |
List module (List modules that are available) | java -p moduleFolder –list-modules |
View Dependencies (information about dependencies within a module by looking into code rather than a module-info file) | jdeps -s –modulepath moduleFolder jarName |
Module Resolution (much more than listing modules, for Debugging) | java –show-module-resolution -p moduleFolder -d moduleName |
Different type of Modules
- Named Modules
- A named module contains a module-info.java file in Jar, also the jar should be in module path.
- Automatic Modules
- A automatic module does not have module-info.java file in Jar, but the jar is in module path.
- The name for automatic module is decided by following order if present-
- Defined in MANIFEST.MF
- JAR name, i.e after ramoving version, and changing values other than letters and numbers to dots.
- Unnamed Modules
- An unnames module does not have module-info.java in Jar, but the jar is in classPath.
Creating a Service
A service has an interface, any classes the interface references, and a way of looking(service locator) the implementations of the interface. The implementations are not part of the service and are termed as Service Provider.
Create Service Interface
Step-1: Create module-info file
module com.jones.moduledemo.engine {
exports com.jones.moduledemo.engine;
}
Step-2: Create Interface file
package com.jones.moduledemo.engine;
public interface Engine {
String engineName();
int acquisitionCost();
int environmentCost();
int maintanenceCost();
int totalCost();m
}
Export com.jones.moduledemo.engine jar file, similar to how we have done above.
Create Service Provider
Step-1: Create module-info file
Provides directive helps us to specify that we provide an implementation of the interface with a specific implementation class.
module com.jones.moduledemo.combustionengines {
requires com.jones.moduledemo.engine;
requires java.base;
provides com.jones.moduledemo.engine.Engine with com.jones.moduledemo.combustionengines.CombustionEngine;
}
Step-2: Create Service Implementation
A service provider(CombustionEngine) is implementing the interface. A side note is that Java allows only one service provider for a service interface in a module. Finally export com.jones.moduledemo.combustionengines module.
package com.jones.moduledemo.combustionengines;
import com.jones.moduledemo.engine.Engine;
public class CombustionEngine implements Engine {
private static Integer INITIAL_COST = 30;
private static String ENGINE_NAME = "Combustion Engine";
private static Integer ENVIRONMENTAL_COST = 15;
private static Integer MAINTANENCE_COST = 15;
public static void main(String[] args) {
System.out.println("From CombustionEngine");
}
@Override
public String engineName() {
return ENGINE_NAME;
}
@Override
public int acquisitionCost() {
return INITIAL_COST;
}
@Override
public int environmentCost() {
return ENVIRONMENTAL_COST;
}
@Override
public int maintanenceCost() {
return MAINTANENCE_COST;
}
@Override
public int totalCost() {
return acquisitionCost() + environmentCost() + maintanenceCost();
}
}
Create Service Consumer
We will update the Vehicle class to consume the Engine interface.
Step-1: Update module-info file
module com.jones.moduledemo.vehicles {
requires com.jones.moduledemo.engine;
provides com.jones.moduledemo.engine.Engine with com.jones.moduledemo.vehicles.ElectricEngine;
uses com.jones.moduledemo.engine.Engine;
}
Step-2: Create second service implementation
Let’s create a second service implementation to be used by our consumers.
package com.jones.moduledemo.vehicles;
import com.jones.moduledemo.engine.Engine;
public class ElectricEngine implements Engine {
private static Integer INITIAL_COST = 40;
private static String ENGINE_NAME = "Electric Engine";
private static Integer ENVIRONMENTAL_COST = 5;
private static Integer MAINTANENCE_COST = 10;
public static void main(String[] args) {
System.out.println("From ElectricEngine");
}
@Override
public String engineName() {
return ENGINE_NAME;
}
@Override
public int acquisitionCost() {
return INITIAL_COST;
}
@Override
public int environmentCost() {
return ENVIRONMENTAL_COST;
}
@Override
public int maintanenceCost() {
return MAINTANENCE_COST;
}
@Override
public int totalCost() {
return acquisitionCost() + environmentCost() + maintanenceCost();
}
}
Step-3: Create service consumer
ServiceLoader Class picks all the service provider implementations and provides them to the application, note that we need to add a CombustionEngine jar in the module path for the service Loader to locate.
package com.jones.moduledemo.vehicles;
import java.util.OptionalInt;
import java.util.ServiceLoader;
import java.util.ServiceLoader.Provider;
import com.jones.moduledemo.engine.Engine;
public class Vehicle {
public static void main(String[] args) {
System.out.println("From The Vehicle class");
Vehicle vehicle = new Vehicle();
vehicle.describeEngine();
}
private void describeEngine() {
OptionalInt maxCost = ServiceLoader.load(Engine.class)
.stream()
.map(Provider::get)
.mapToInt(Engine::totalCost)
.max();
maxCost.ifPresent(System.out::println);
OptionalInt minCost = ServiceLoader.load(Engine.class)
.stream()
.map(Provider::get)
.mapToInt(Engine::totalCost)
.min();
minCost.ifPresent(System.out::println );
}
}
Step-4: Output
The output shows that both Service Providers’ values are printed on the console. Also, the module path is shown in the screenshot.
Opinion of JPMS
JPMS’s primary aim was to reduce the size of JRE, which although completed but took a lot of time to complete. Converting old projects to modules may not be the right approach for a project. There are many open-source dependencies that real-world projects would be using and probably they won’t be migrating to Modules. I would comment that it would be wise to start new projects with modules and leave old projects as it is for now.
I started with command-line options in creating modules, however, the approach was unfriendly, hence I tried to create a Java project in eclipse using java11. This was the simpler approach and writing code was easier, however, I faced some issues with the module not being found a few times, and those times I just created a new project and copied the Java classes.