Since Java 9, Java release a feature called Module. Module allows a jar to declare availability of packages and its dependencies of packages from other jars, thus reduce the chance of ClassNotFoundException and increase encapsulation.

Configuration
Java Compilation:
Java Runtime:
Maven:
Java Compilation:
Java Runtime:
Gradle:
JDK 11.0.12
JRE HotSpot 11.0.12
Maven 3.8.4
JDK 11.0.12
JRE HotSpot 11.0.12
Gradle 7.4.2
This tutorial will show you how to convert a Java project with regular Jars to Modular Jars
Assuming you have the following Maven project structure before conversion, where the main pom.xml is a parent pom with 3 Maven modules, with MainJar which contains the main class. MainJar uses a class in Reference2, which uses a class in Reference1:
folder structure of Convert a Java project to Modular format
pom.xml: (if using Gradle, replace pom.xml with build.gradle and add settings.gradle in parent folder)
parent pom.xml:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>net.maxjava</groupId> <artifactId>ConvertToModularJar_before</artifactId> <packaging>pom</packaging> <version>1.0</version> <modules> <module>Reference1</module> <module>Reference2</module> <module>MainJar</module> </modules> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>11</source> <target>11</target> </configuration> </plugin> </plugins> </build> </project>
MainJar pom.xml:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>ConvertToModularJar_before</artifactId> <groupId>net.maxjava</groupId> <version>1.0</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>MainJar</artifactId> <dependencies> <dependency> <groupId>net.maxjava</groupId> <artifactId>Reference2</artifactId> <version>1.0</version> </dependency> </dependencies> </project>
Reference2 pom.xml:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>ConvertToModularJar_before</artifactId> <groupId>net.maxjava</groupId> <version>1.0</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>Reference2</artifactId> <dependencies> <dependency> <groupId>net.maxjava</groupId> <artifactId>Reference1</artifactId> <version>1.0</version> </dependency> </dependencies> </project>
Reference1 pom.xml:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>ConvertToModularJar_before</artifactId> <groupId>net.maxjava</groupId> <version>1.0</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>Reference1</artifactId> </project>
parent build.gradle:
plugins { id 'java' } sourceCompatibility = 11 targetCompatibility = 11 group 'net.maxjava' version '1.0' repositories { mavenCentral() }
parent settings.gradle:
rootProject.name = 'ConvertToModularJar_before' include 'MainJar' include 'Reference1' include 'Reference2'
MainJar build.gradle:
plugins { id 'java' } group 'net.maxjava' version '1.0' repositories { mavenCentral() } dependencies { implementation project(':Reference2') }
Reference2 build.gradle:
plugins { id 'java' } group 'net.maxjava' version '1.0' repositories { mavenCentral() } dependencies { implementation project(':Reference1') }
Reference1 build.gradle:
plugins { id 'java' } group 'net.maxjava' version '1.0' repositories { mavenCentral() }
MainClass.java:
package net.maxjava; import net.maxjava2.Library2; public class MainClass { public static void main(String[] arg) { int radius = 10; System.out.println("Area of circle with radius " + radius + " = " + Library2.getArea(radius)); } }
Library1.java:
package net.maxjava1; public class Library1 { public static float getPI() { return 3.14f; } }
Library2.java:
package net.maxjava2; import net.maxjava1.Library1; public class Library2 { public static float getArea(int radius) { return radius * radius * Library1.getPI(); } }
After running the Maven command mvn package, all 3 of the jar files (MainJar-1.0.jar, Reference1-1.0.jar and Reference2-1.0.jar) will be generated, inside the target folder of each of the Maven module respectively.
  • on Windows, to run the main function, use the command:
    java -cp MainJar/target/MainJar-1.0.jar;Reference1/target/Reference1-1.0.jar;Reference2/target/Reference2-1.0.jar net.maxjava.MainClass
  • on Linux/Mac, to run the main function, use the command:
    java -cp MainJar/target/MainJar-1.0.jar:Reference1/target/Reference1-1.0.jar:Reference2/target/Reference2-1.0.jar net.maxjava.MainClass
After running the Gradle command gradle build, all 3 of the jar files (MainJar-1.0.jar, Reference1-1.0.jar and Reference2-1.0.jar) will be generated, inside the build/libs folder of each of the Maven module respectively.
  • on Windows, to run the main function, use the command:
    To run the main function, use the command java -cp MainJar/build/libs/MainJar-1.0.jar;Reference1/build/libs/Reference1-1.0.jar;Reference2/build/libs/Reference2-1.0.jar net.maxjava.MainClass
  • on Linux/Mac, to run the main function, use the command:
    To run the main function, use the command java -cp MainJar/build/libs/MainJar-1.0.jar:Reference1/build/libs/Reference1-1.0.jar:Reference2/build/libs/Reference2-1.0.jar net.maxjava.MainClass
Output:
Area of circle with radius 10 = 314.0
Converting to modular jars:
To convert this Jave project (meaning all the 3 Jar files) to modular format, all we need is, for each module, create a file name module-info.java under the package root folder:
folder structure of Convert a Java project to Modular format
In module-info.java, specify a module and the availability of packages under this module. E.g. in Reference1 module-info.java, it exports net.maxjava1 so that this package is available in this module. In Reference2 module-info.java, it exports net.maxjava1 and requires max1 module, meaning net.maxjava2 is available in this module and it requires max1 module for compiling and running. In MainJar module-info.java, it requires max2 module meaning max2 module is needed for compiling and running
There is no need to change any class or package, and the pom/gradle file.
Reference1 module-info.java:
module max1 { exports net.maxjava1; }
Reference2 module-info.java:
module max2 { requires max1; exports net.maxjava2; }
MainJar module-info.java:
module max { requires max2; }
The Maven command to build is still mvn package, and still 3 jar files (MainJar-1.0.jar, Reference1-1.0.jar and Reference2-1.0.jar) will be generated, inside the target folder of each of the Maven module respectively.
The command to run has to change, specification of module-paths is needed and main class has to qualified with module name:
  • on Windows, to run the main function, use the command:
    java --module-path Reference1/target/Reference1-1.0.jar;Reference2/target/Reference2-1.0.jar;MainJar/target/MainJar-1.0.jar -m max/net.maxjava.MainClass
  • on Linux/Mac, to run the main function, use the command:
    java --module-path Reference1/target/Reference1-1.0.jar:Reference2/target/Reference2-1.0.jar:MainJar/target/MainJar-1.0.jar -m max/net.maxjava.MainClass
After running the Gradle command gradle build, all 3 of the jar files (MainJar-1.0.jar, Reference1-1.0.jar and Reference2-1.0.jar) will be generated, inside the build/libs folder of each of the Gradle module respectively.
The command to run has to change, specification of module-paths is needed and main class has to qualified with module name:
  • on Windows, to run the main function, use the command:
    java --module-path Reference1/build/libs/Reference1-1.0.jar;Reference2/build/libs/Reference2-1.0.jar;MainJar/build/libs/MainJar-1.0.jar -m max/net.maxjava.MainClass
  • on Linux/Mac, to run the main function, use the command:
    java --module-path Reference1/build/libs/Reference1-1.0.jar:Reference2/build/libs/Reference2-1.0.jar:MainJar/build/libs/MainJar-1.0.jar -m max/net.maxjava.MainClass
Output is the same:
Area of circle with radius 10 = 314.0
If a module is missing in the module-path when running, a java.lang.module.FindException will come
e.g. if module max2 is missing:
java --module-path Reference1/target/Reference1-1.0.jar:MainJar/target/MainJar-1.0.jar -m max/net.maxjava.MainClass
Output:
Error occurred during initialization of boot layer
java.lang.module.FindException: Module max2 not found, required by max
e.g. if module max2 is missing:
java --module-path Reference1/build/libs/Reference1-1.0.jar:MainJar/build/libs/MainJar-1.0.jar -m max/net.maxjava.MainClass
Output:
Error occurred during initialization of boot layer
java.lang.module.FindException: Module max2 not found, required by max