Java 9 Modules (Part 1): Introduction
Get your feet wet with Java 9's modularity by learning how to create, compile, and execute single- and multi-module projects all from the command line.
Join the DZone community and get the full member experience.
Join For Free(PIn this post, we will introduce the Java Platform Module System (JPMS), which is the biggest change in the Java 9 release. In this post, we will take a look at some basics of the JPMS (Why do we need modules? What has changed in the JDK?). After that, we will take a look at how a single-module application can be created, compiled, and executed. At the end, we will take a look at how a multi-module application can be created, compiled, and executed as well. In this post, we will only use command line tools. The examples used can be found on GitHub.
Why?
Let's start with the fundamental question: Why do we need modules anyway? In order to answer that question, we take a look at JSR-376. The following bullets are copied from the JSR:
- Reliable configuration — Developers have long suffered with the brittle, error-prone class-path mechanism for configuring program components. The class path cannot express relationships between components, so if a necessary component is missing then that will not be discovered until an attempt is made to use it. The class path also allows classes in the same package to be loaded from different components, leading to unpredictable behavior and difficult-to-diagnose errors. The proposed specification will allow a component to declare that it depends upon other components, as other components depend upon it.
- Strong encapsulation — The access-control mechanism of the Java programming language and the Java virtual machine provides no way for a component to prevent other components from accessing its internal packages. The proposed specification will allow a component to declare which of its packages are accessible by other components, and which are not.
- A scalable platform — The ever-increasing size of the Java SE Platform has made it increasingly difficult to use in small devices despite the fact that many such devices are capable of running an SE-class Java virtual machine. The Compact Profiles introduced in Java SE 8 (JSR 337) help in this regard, but they are not nearly flexible enough. The proposed specification will allow the Java SE Platform, and its implementations, to be decomposed into a set of components which can be assembled by developers into custom configurations that contain only the functionality actually required by an application.
- Greater platform integrity — Casual use of APIs that are internal to Java SE Platform implementations is both a security risk and a maintenance burden. The strong encapsulation provided by the proposed specification will allow components that implement the Java SE Platform to prevent access to their internal APIs.
- Improved performance — Many ahead-of-time, whole-program optimization techniques can be more effective when it is known that a class can refer only to classes in a few other specific components rather than to any class loaded at run time. Performance is especially enhanced when the components of an application can be optimized in conjunction with the components that implement the Java SE Platform.
Modules: Some Basics
Java Modules
In order to see which modules are available within Java, we can enter the following command:
java --list-modules
A list is shown with the available modules. Below is an extract from the list:
java.activation@9
java.base@9
java.compiler@9
...
From the list, we can see that the modules are split into four categories: the ones starting with java (standard Java modules), the ones starting with javafx (JavaFX modules), the ones starting with jdk (JDK-specific modules) and the ones starting with oracle (Oracle-specific modules). Each module ends with @9, indicating that the module belongs to Java 9.
Module Declaration
Module properties are located in a module-info.java file. In order to see the description of the module defined in this file, the following command can be used:
java --describe-module java.sql
This will output the following:
java.sql@9
exports java.sql
exports javax.sql
exports javax.transaction.xa
requires java.logging transitive
requires java.xml transitive
requires java.base mandated
uses java.sql.Driver
The exports keyword indicates that these packages are available to other modules. This means that public classes are, by default, only public within the module unless it is specified within the module info declaration.
The requires keyword indicates that this module depends on another module.
The uses keyword indicates that this module uses a service.
Javadoc
Take also a look at the Java 9 Javadoc. As you will see, it is now divided into modules instead of into packages. Inside a module's Javadoc, you find a Module Graph and the packages. Actually, the same structure as above can be found with --describe-module.
Create Your Own JRE
The directory structure of the JDK has changed a bit. A directory, jmods, is added. This directory contains a jmod file for each module. A jmod file is like a JAR file, but for modules. You can unzip the file and look at the contents. It contains the compiled classes for this module.
The size of the whole JDK is 482 MB on my laptop. When using jlink, we can create our own JRE with only the modules we are using in our application. The following command will create a JRE with only the java.base module.
jlink --module-path ../jmods --add-modules java.base --output d:\java\jre
The size of this runtime is only 36 MB. Pretty easy isn't it?
Hello Modules!
Now it's time for some real action!
Example Explained
In the next sections, we will use a Hello Modules example. The following directory structure will be used:
mods
src
|_ com.mydeveloperplanet.jpmshello - module-info.java
|_ com
|_mydeveloperplanet
|_ jpmshello - HelloModules.java
target
The mods directory will be used for the compiled modules and classes.
The src directory will be used for our sources. Inside this directory, we first have a directory com.mydeveloperplanet.jpmshello. This is the top-level directory for our module. It uses the same naming convention as for packages. Inside this directory, a file named module-info.java exists. The content will be shown and explained later on.
Inside the module directory, we have the package structure that is already familiar to us. We have one HelloModules.java class. The target directory will be used to store our JAR file in.
Below, the contents of the HelloModules.java class is shown. It is a simple HelloModules example: one Main class, and it resides in the package com.mydeveloperplanet.jpmshello.
package com.mydeveloperplanet.jpmshello;
public class HelloModules {
public static void main(String[] args) {
System.out.println("Hello Modules!");
}
}
The contents of the module-info.java is as follows.
module com.mydeveloperplanet.jpmshello {
requires java.base;
}
After the keyword module, the name of our module is set. Inside the brackets, we define which standard Java modules we use in our module. In our case, we only use the java.base module. We define the usage by using the keyword requires. It is not necessary to explicitly add the java.base module because it is always implicitly added. But in our example and for clarity, we added it anyway.
Compilation
The next step is to compile the classes. In this post, we will use the command line in order to compile and execute the application — always a good thing to do. This way, you better understand how things work. Later on, we will take a look at the IDE support and how it works when using Maven.
With the following command, we compile the application. The -d option specifies the destination directory for the compiled classes. In our case, we compile it to a subdirectory, com.mydeveloperplanet.jpmshello, which resembles our module. At the end, we specify the classes to be compiled.
javac -d mods/com.mydeveloperplanet.jpmshello src/com.mydeveloperplanet.jpmshello/module-info.java src/com.mydeveloperplanet.jpmshello/com/mydeveloperplanet/jpmshello/HelloModules.java
Execution
To execute the compiled classes, we need to set the module-path. This option sets the directories where the modules can be found. The module option sets the main class that must be invoked. It is necessary to set the module and the package and the class where the main class is located.
java --module-path mods --module com.mydeveloperplanet.jpmshello/com.mydeveloperplanet.jpmshello.HelloModules
The output is:
Hello Modules!
Create a JAR File
The above example is already cool, but we also want to package the application in a JAR file in order to be able to distribute it easily. We create the JAR file with the command below (prerequisite is that the target directory exists). The file option specifies the location and name of the JAR file. The main-class option specifies the entry point of the application — in other words, where the main class resides. The C option specifies the location of the classes to include in the JAR file:
jar --create --file target/jpms-hello-modules.jar --main-class com.mydeveloperplanet.jpmshello.HelloModules -C mods/com.mydeveloperplanet.jpmshello .
Now we can execute the application from the JAR file:
java --module-path target/jpms-hello-modules.jar --module com.mydeveloperplanet.jpmshello/com.mydeveloperplanet.jpmshello.HelloModules
The output is, again:
Hello Modules!
We can also check the module description as we did before with the Java standard modules:
java --module-path target/jpms-hello-modules.jar --describe-module com.mydeveloperplanet.jpmshello
This outputs the following as expected:
requires java.base
contains com.mydeveloperplanet.jpmshello
In the first section, we created a custom JRE with only the java.base module. Now run the JAR file using this custom JRE. As you will see, the output again prints Hello Modules!
Import a Package From Another Module
We will extend the example by using a package from a module other than java.base. We will do so by using the java.xml.XMLConstants.XML_NS_PREFIX and print it to the console.
The HelloModules.java class becomes the following.
package com.mydeveloperplanet.jpmshello;
import static javax.xml.XMLConstants.XML_NS_PREFIX;
public class HelloModules {
public static void main(String[] args) {
System.out.println("Hello Modules!");
System.out.println("The XML namespace prefix is: " + XML_NS_PREFIX);
}
}
Compile it just as we did before. We did not import the javax.xml module, so it is expected that the compilation fails. It does so with the following error on the import statement:
error: static import only from classes and interfaces
Of course, we know how to fix this — we have to indicate in the module-info.java that we require the java.xml module. It becomes the following:
module com.mydeveloperplanet.jpmshello {
requires java.base;
requires java.xml;
}
Now compile, create the JAR file, and run the application. The output is:
Hello Modules!
The XML namespace prefix is: xml
Create a Multi-Module Application
Let's take it one step further. We are going to add another module, com.developerplanet.himodule, to the application and use a class, HiModules, from the other module in our main module. The directory structure becomes:
mods
src
|_ com.mydeveloperplanet.jpmshello - module-info.java
| |_ com
| |_mydeveloperplanet
| |_ jpmshello - HelloModules.java
|_ com.mydeveloperplanet.jpmshi - module-info.java
|_ com
|_ mydeveloperplanet
|_ jpmshi - HiModules.java
target
The contents of HiModules.java are:
package com.mydeveloperplanet.jpmshi;
public class HiModules {
public String getHi() {
return "Hi Modules!";
}
}
The contents of module-info.java are the following:
module com.mydeveloperplanet.jpmshi {
}
We extend our HelloModules.java class in order to use the HiModules class.
package com.mydeveloperplanet.jpmshello;
import com.mydeveloperplanet.jpmshi.HiModules;
import static javax.xml.XMLConstants.XML_NS_PREFIX;
public class HelloModules {
public static void main(String[] args) {
System.out.println("Hello Modules!");
System.out.println("The XML namespace prefix is: " + XML_NS_PREFIX);
HiModules hiModules = new HiModules();
System.out.println(hiModules.getHi());
}
}
Compile a Multi-Module Project
We compile the multi-module project. Compared to the single module project, we change the compilation command as follows:
- The modules output path (-d option) is changed to mods
- We add the module-source-path option in order to specify where the modules source path is
- We add the sources of module com.mydeveloperplanet.jpmshi to the list of the be compiled classes
javac -d mods --module-source-path src src/com.mydeveloperplanet.jpmshello/module-info.java src/com.mydeveloperplanet.jpmshello/com/mydeveloperplanet/jpmshello/HelloModules.java src/com.mydeveloperplanet.jpmshi/module-info.java src/com.mydeveloperplanet.jpmshi/com/mydeveloperplanet/jpmshi/HiModules.java
But compilation fails with the following error:
error: package com.mydeveloperplanet.jpmshi is not visible
We forgot two things:
- We need to tell module com.mydeveloperplanet.jpmshello that it requires module com.mydeveloperplanet.jpmshi
- We need to tell module com.mydeveloperplanet.jpmshi that package com.mydeveloperplanet.jpmshi is exported and therefore may be used by other modules
The module-info.java from module com.mydeveloperplanet.jpmshello becomes:
module com.mydeveloperplanet.jpmshello {
requires java.base;
requires java.xml;
requires com.mydeveloperplanet.jpmshi;
}
The module-info.java from module com.mydeveloperplanet.jpmshi becomes:
module com.mydeveloperplanet.jpmshi {
exports com.mydeveloperplanet.jpmshi;
}
After these changes, the compilation is successful and the compiled classes can be found in the mods directory.
Create JAR Files for a Multi-Module Project
Now it is time to create the JAR files. For each module, we need to create a JAR file.
Create the JAR file for the module com.mydeveloperplanet.jpmshi:
jar --create --file target/jpms-hi-modules.jar -C mods/com.mydeveloperplanet.jpmshi .
Create the JAR file for the module com.mydeveloperplanet.jpmshello. This is the same command we used for creating the JAR file for the single module project:
jar --create --file target/jpms-hello-modules.jar --main-class com.mydeveloperplanet.jpmshello.HelloModules -C mods/com.mydeveloperplanet.jpmshello .
Execute a Multi-Module Project
And now run the application:
java --module-path target --module com.mydeveloperplanet.jpmshello/com.mydeveloperplanet.jpmshello.HelloModules
The output is:
Hello Modules!
The XML namespace prefix is: xml
Hi Modules!
Running this example with our custom JRE fails with the following error:
Error occurred during initialization of boot layer
java.lang.module.FindException: Module java.xml not found, required by com.mydeveloperplanet.jpmshello
This is what we could expect, as the custom JRE did not include the module java.xml.
Let's create the custom JRE with the module java.xml included:
jlink --module-path ../jmods --add-modules java.base,java.xml --output d:\java\jre
Run the example again with the custom JRE, and now it runs successfully!
Summary
In this post, we took our first steps within the Java Platform Module System. We took a look at some theory and after that, we created, compiled, and executed a single module application. At the end, we created, compiled, and executed a multi-module application. We also covered some basic keywords of the module-info.java class. In this post, we did everything by means of the command line, but in the next post, we will take a look at how we can accomplish the above when working with an IDE and Maven.
Published at DZone with permission of Gunter Rotsaert, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments