Javac and Java Katas, Part 2: Module Path
In this article, look at some exercises dedicated to using JDK tools such as javac, java, and jar to build and run modular Java applications.
Join the DZone community and get the full member experience.
Join For FreeThis is Part 2, a continuation of Javac and Java Katas, Part 1: Class Path, where we will run through the same exercises (katas) but this time the main focus will be the usage of the Java Platform Module System.
Getting Started
As in Part 1, all commands in this article are executed inside a Docker container to make sure that they work and to mitigate any environment-specific setup.
So, let's clone the GitHub repository and run the command below from its java-javac-kata folder:
docker run --rm -it --name java_kata -v .:/java-javac-kata --entrypoint /bin/bash maven:3.9.6-amazoncorretto-17-debian
Kata 1: "Hello, World!" Warm Up
We will start with a primitive Java application, /module-path-part/kata-one-hello-world-warm-up, which does not have any third-party dependencies. The directory structure is as follows:

In the picture above, we can see the Java project package hierarchy with two classes in the com.example.kata.one package and the module-info.java file which is a module declaration.
Compilation
To compile our code, we are going to use javac in the single-module mode, which implies that the module-source-path option is not used:
javac -d ./target/classes $(find -name '*.java')

As a result, the compiled Java classes should appear in the target/classes folder. The verbose option can provide more details on the compilation process:
javac -verbose -d ./target/classes $(find -name '*.java')
We can also obtain the compiled module description as follows:
java --describe-module com.example.kata.one --module-path target/classes
Execution
java --module-path target/classes --module com.example.kata.one/com.example.kata.one.Main
What should result in Hello World! in your console. Various verbose:[class|module|gc|jni] options can provide more details on the execution process:
java -verbose:module --module-path target/classes --module com.example.kata.one/com.example.kata.one.Main
Also, experimenting a bit during both the compilation and execution stages, by removing or changing classes and packages, should give you a good understanding of which issues lead to particular errors.
Packaging
Building Modular JAR
According to JEP 261: Module System, "A modular JAR file is like an ordinary JAR file in all possible ways, except that it also includes a module-info.class file in its root directory. " With that in mind, let's build one:
jar --create --file ./target/hello-world-warm-up.jar -C target/classes/ .

The jar file is placed in the target folder. Also, using the verbose option can give us more details:
jar --verbose --create --file ./target/hello-world-warm-up.jar -C target/classes/ .
You can view the structure of the built jar by using the following command:
jar -tf ./target/hello-world-warm-up.jar
And get a module description of the modular jar:
jar --describe-module --file ./target/hello-world-warm-up.jar
Additionally, we can launch the Java class dependency analyzer, jdeps, to gain even more insight:
jdeps ./target/hello-world-warm-up.jar
As usual, there is the verbose option, too:
jdeps -verbose ./target/hello-world-warm-up.jar
With that, let's proceed to run our modular jar:
java --module-path target/hello-world-warm-up.jar --module com.example.kata.one/com.example.kata.one.Main
Building Modular Jar With the Main Class
jar --create --file ./target/hello-world-warm-up.jar --main-class=com.example.kata.one.Main -C target/classes/ .
Having specified the main-class, we can run our app by omitting the <main-class> part in the module option:
java --module-path target/hello-world-warm-up.jar --module com.example.kata.one
Kata 2: Third-Party Dependency
Let's navigate to the /module-path-part/kata-two-third-party-dependency project and examine its structure.

This kata is also a Hello World! application, but with a third-party dependency, guava-30.1-jre.jar, which has an automatic module name, com.google.common. You can check its name by using the describe-module option:
jar --describe-module --file lib/guava-30.1-jre.jar
Compilation
javac --module-path lib -d ./target/classes $(find -name '*.java')
The module-path option points to the lib folder that contains our dependency.
Execution
java --module-path "target/classes:lib" --module com.example.kata.two/com.example.kata.two.Main
Building Modular Jar
jar --create --file ./target/third-party-dependency.jar --main-class=com.example.kata.two.Main -C target/classes/ .
Now, we can run our application as follows:
java --module-path "lib:target/third-party-dependency.jar" --module com.example.kata.two
Kata 3: Spring Boot Application Conquest
In the /module-path-part/kata-three-spring-boot-app-conquest folder, you will find a Maven project for a primitive Spring Boot application. To get started with this exercise, we need to execute the script below.
./kata-three-set-up.sh
The main purpose of this script is to download all necessary dependencies into the ./target/lib folder and remove all other files in the ./target directory.

As seen in the picture above, the ./target/lib has three subdirectories. The test directory contains all test dependencies. The automatic-module stores dependencies used by the module declaration. The remaining dependencies used by the application are put into the unnamed-module directory. The intention of this separation will become clearer as we proceed.
Compilation
javac --module-path target/lib/automatic-module -d ./target/classes/ $(find -P ./src/main/ -name '*.java')
Take notice that for complications, we only need the modules specified in the module-info.java, which are stored in the automatic-module directory.
Execution
java --module-path "target/classes:target/lib/automatic-module" \
--class-path "target/lib/unnamed-module/*" \
--add-modules java.instrument \
--module com.example.kata.three/com.example.kata.three.Main
As a result, you should see the application running.
For a better understanding of how the class-path option works here together with the module-path, I recommend reading the 3.1: The unnamed module part of "The State of the Module System."
Building Modular Jar
Let's package our compiled code as a modular jar, with the main class specified:
jar --create --file ./target/spring-boot-app-conquest.jar --main-class=com.example.kata.three.Main -C target/classes/ .
Now, we can run it:
java --module-path "target/spring-boot-app-conquest.jar:target/lib/automatic-module" \
--class-path "target/lib/unnamed-module/*" \
--add-modules java.instrument \
--module com.example.kata.three
Test Compilation
For simplicity's sake, we will use the class path approach to run tests here. There's little benefit in struggling with tweaks to the module system and adding additional options to make the tests work. With that, let's compile our test code:
javac --class-path "./target/classes:./target/lib/automatic-module/*:./target/lib/test/*" -d ./target/test-classes/ $(find -P ./src/test/ -name '*.java')
Test Execution
java --class-path "./target/classes:./target/test-classes:./target/lib/automatic-module/*:./target/lib/unnamed-module/*:./target/lib/test/*" \
org.junit.platform.console.ConsoleLauncher execute --scan-classpath --disable-ansi-colors
For more details, you can have a look at Part 1 of this series (linked in the introduction), which elaborates on the theoretical aspect of this command.
Wrapping Up
That's it. I hope you found this useful, and that these exercises have provided you with some practical experience regarding the nuances of the Java Platform Module System.
Opinions expressed by DZone contributors are their own.
Comments