In this final installment on modules, we’ll look at a simple example of compiling and packaging a module.
If you missed the previous posts in this sequence, you can find them here:
- Java Modularity Part 1 – Introduction
- Java Modularity Part 2 – Keywords and Descriptors
- Java Modularity Part 3 – Requires and Exports
- Java Modularity Part 4 – Services
- Java Modularity Part 5 – Reflection
- Java Modularity Part 6 – Unnamed and Automatic Modules
Java Modularity Example
Let’s use the standard “Hello, world!” code that’s been used a million times before. In this example however, we’ll modularize it.
package com.incusdata.test; public class ModularHelloWorld { public static void main (String args[]) { System.out.println("Hello, modular world!"); } } // end of class
Directory structure
There’s nothing exotic or new about the Java code. What does change, however, is the directory structure where we place the code. We would have previously stored the ModularHelloWorld.java
file under a directory hierarchy with the same names as the package name, as in com\incusdata\test\ModularHelloWorld.java
.
Let’s assume that we would like to create a module called com.incusdata.test
. Following a common module naming convention, our package directory structure would now placed under a directory with the same name as the module, hence com.incusdata.test\com\incusdata\test\ModularHelloWorld.java
.
We would also create a module-info.java
file in the com.incusdata.test
directory.
For the example, our full directory structure is the following:
src |_ com.incusdata.test - module-info.java |_ com |incusdata |_ test - ModularHelloWorld.java mods target
The src
directory will be used for our source code. The mods
directory will be used for the compiled modules and classes. The target
directory will be used for the resulting JAR file.
The contents of the module-info.java
is as follows.
module com.incusdata.test { requires java.base; }
We’ve already seen modules in some of the previous posts. Within the module, we need to define the Java modules we require. In this case, we only require the java.base
module. We added it for clarity, rather than necessity because it is always implicitly added.
Compiling the code
For this example, we’ll be compiling from the command line. By doing this, we get a better understanding of how things work.
javac -d mods/com.incusdata.test src/com.incusdata.test/module-info.java src/com.incusdata.test/com/incusdata/test/ModularHelloWorld.java
The -d
option specifies the destination directory for the compiled classes. We compile it to a directory – com.incusdata.test
– which has the same name as our module.
Running the program
To run the compiled program, we need to set the --module-path
. This command-line option sets the directories where the modules can be found. The --module
option sets the main class that is called.
java --module-path mods --module com.incusdata.test/com.incusdata.test.ModularHelloWorld
The output will be Hello, modular world!
as we would expect.
Creating a JAR file
When we create a module, we’d generally like to package it as a JAR
file for easy distribution.
The command to do this is the following:
jar --create --file target/test-modular-helloworld.jar --main-class com.incusdata.test.ModularHelloWorld -C mods/com.incusdata.test
The target
directory must exist before running the jar
command. The --file
option specifies the JAR
file name and directory. The --main-class
obviously specifies the main class or entry point. The (uppercase) -C
option specifies the files to include in the JAR
file.
We can execute the application for the JAR
file with the following command:
java --module-path target/test-modular-helloworld.jar --module com.incusdata.test/com.incusdata.test.ModularHelloWorld
To check on the module description, we can use the --describe-module
option as follows:
java --module-path target/test-modular-helloworld.jar --describe-module com.incusdata.test
Importing a package
The previous example was very simple, but in a real-world application, we would want to import packages for other modules as well. Let’s import a constant, say java.awt.BorderLayout.NORTH
and print it out:
package com.incusdata.test; import static java.awt.BorderLayout.NORTH; public class ModularHelloWorld { public static void main (String args[]) { System.out.println("Hello, modular world!"); System.out.println("java.awt.BorderLayout.NORTH: " + NORTH); } } // end of class
If we compile it as before, the compilation fails. This is because we didn’t import the java.desktop
module which includes the java.awt
package. We need to change the module-info.java
to require the java.desktop
module:
module com.incusdata.test { requires java.base; requires java.desktop; requires java.xml; }
Compiling, packaging and running it as before will give us the correct output.
What’s Next?
I’ve just introduced the concepts of compiling and packaging modules. For more information, DZone has a number of good articles on modularity. A first stop would be their introduction to modules. Jenkov.com also has a very detailed tutorial.
I hope you found this series on modularity useful. Please share your comments and any questions you have.