Java 11 Developer Certification - Modules Command line support & graphs
May 24, 2021
Command Line Tool options to examine modules
The below tables summarize the command line tool options in a detailed manner for each executable type.
java Executable
Executable | Java Options | Examples | Explanation |
---|---|---|---|
java | —list-modules | java —list-modules java -p MyFirstModule.jar —list-modules |
List all the visible modules. If no module path is specified, you will get a listing of the modular Java Development Kit. If you specify directories in the module path, you will include visible modules in those directories (either exploded directories or jar files) |
java | -p —module-path |
java —module-path . -m MyFirstModule java -p . -m MyFirstModule |
You need to specify a module path when executing a module jar |
java | -m —module |
java -p . -m MyFirstModule java -p . —module MyFirstModule |
You need to specify a module name when executing a module jar |
java | —dry-run | java —dry-run -p . —module MyFirstModule | The —dry-run option loads the main class but does not run it, used for testing module dependencies are satisfied. A successful result is no output |
java | -d —describe-module |
java —module-path . —describe-module MyFirstModule java -p . -d MyFirstModule |
This option gives you summary information about a module including its path, its dependencies, and its packages and resources |
javac Executable
Executable | Java Options | Examples | Explanation |
---|---|---|---|
javac | —module -m |
javac -d out\production —module-source-path . —module MyFirstModule javac -d out\production —module-source-path . -m MyFirstModule |
When you are compiling using —module, you must also specify —module-source-path |
javac | -p —module-path |
javac -d out\production -p . —module-source-path . —m MyFirstModule | You use —module-path with javac when your code has a dependency on existing modules |
jar Executable
Executable | Java Options | Examples | Explanation |
---|---|---|---|
jar | -d —describe-module |
jar —file MyFirstModule.jar —describe-module jar -f MyFirstModule.jar -d |
The —describe-module option for the jar command is similar to the java one, but includes the main class as well |
jdeps Executable
Executable | Java Options | Examples | Explanation |
---|---|---|---|
jdeps | jdeps MyFirstModule.jar | jdeps with no options will give you the summary information about the jar file, including module dependencies if this is a modular jar | |
jdeps | —list-deps | jdeps —module-path .;out —list-deps MyFirstModule.jar jdeps —module-path .;out —list-deps —module MyFirstModule jdeps —module-path .;out —list-deps -m MyFirstModule |
—list-deps option will list module dependencies. You can either use a specified jar or a module with the -m option It is important to note that for jdeps. -p is NOT interchangeable with —module-path |
jdeps | —list-reduced-deps | jdeps —module-path .;out —list-reduced-deps -m MyFirstModule jdeps —list-reduced-deps MyFirstModule jdeps —list-reduced-deps —module java.sql |
Same as —list-deps but does not include implied reads |
jdeps | —print-module-deps | jdeps —print-module-deps —module java.sql jdeps -module-path . —print-module-deps MyFirstModule.jar |
Same as —list-reduced-deps, but prints the dependencies in a comma delimited list |
jdeps | —check | jdeps —module-path . —check MyFirstModule jdeps —checl java.sql |
Prints the module descriptor, the resulting module dependencies after analysis and the graph after transition reduction. It also identifies any unused qualified exports |
Now that we have seen these options, lets create a second module
Second module module-info.java
module MySecondModule {
exports mod2;
}
First module module-info.java
module MyFirstModule {
requires MySecondModule;
}
GoodbyeWorld class
package mod2;
public class GoodbyeWorld {
public static void main(String[] args) {
System.out.println("And that's a wrap - goodbye.");
}
}
HelloWorld class
package modular;
import mod2.GoodbyeWorld;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello new modular world");
GoodbyeWorld.main(args);
}
}
If you are working in IntelliJ or Eclipse IDE, these IDEs figure out the dependencies and adjusts our module-info.java files accordingly, specifying which package needs to be exported in MySecondModule and which module is required by MyFirstModule.
On building this, we get the out folder and two modules there, MyFirstModule and MySecondModule and they’ve both got the appropriate subfolders. They’ve got a package name and also, have the module info class file for each.
Now with this configuration, we can try out all the options that we have mentioned above, with java, javac, jar and jdeps.
So it’s important to remember that for jdeps, unlike the other tools -p is not the same as —module-path.
So to sum it up, we saw
- how to build and execute a module
- how to jar the module
- executing both jarred module and the exploded directory from the command line.
- we have gone in and we worked through multiple examples of using the tools to get information about modules.
- And saw a brief sample of how to create modules that work together.
Enabling access between modules
In the previous sections we saw the module-info.java file, and stated that it was required to configure either a jar or exploded directory as a module.
So at a minimum, the module-info.java file, which is a multi-directive file for module named NamedModule has this form:
module NamedModule { }
The official specification for the module declaration is as below:
ModuleDeclaration:
{Annotation} [open] module Identifier {. Identifier} { {Module Directive} }
From the java specification, the following directives should populate {ModuleDirective} are as follows:
Module Directive | Type | Directive Arguments | Examples | Description |
---|---|---|---|---|
requires | {RequiresModifier} ModuleName | requires org.module.a; requires java.logging; requires transitive org.module.d; requires static org.module.e; |
A module specified in the requires directive is one which exports package or packages that the current module may or does have a dependency on. The current module we said to ‘read’ the module specified in the required directive. A requires directive with a transitive modifier, allows any module, which requires the current module to have an implicit ‘requires directive’ on the specified module. And a static directive, requires the specified module at compile time, but it’s optional at run time. |
|
exports | unqualified | PackageName | exports org.pkg.base; | A package specified in the exports directive will expose it’s public and protected types and the public and protect members of those types, to modules that read the current module. So in other words, specifies a requires directive |
to the current module. | ||||
exports | qualified | PackageName [to ModuleName {,ModuleName}] | exports org.pkg.util to org.module.a, org.module.b | The ‘to’ key word makes an exports directive qualified and will be followed by a comma delimited list of modules that are termed ‘friends’ of the current module. So friends of a module have access to public and protected types, and the public and protected members of those types, of the exported package. |
And no other modules will have access and you limit the exposure of the export package types to its friends. |
Continuing with the last code, we will create a new module org.module.global. If you are doing this in IntelliJ, you need to right click on the src folder within this module, and create the module-info.java file manually.
module org.module.global {
exports maxCode.online.module;
}
We will create the below ApplicationConstants class, that has two fields, one constant APP_NAME, and one that is a global counter which we provide two methods for addCounter and getCounter. Basically we want to increment and want to retrieve the value.
package maxCode.online.module;
// Set up a public class with some global static fields
public class ApplicationConstants {
// APP_NAME is a constant
public static final String APP_NAME = "Module Test";
// counter will be a global counter
private static int counter;
// increment counter
public static void addCounter() {
counter += 1;
}
// retrieve value of the counter
public static int getCounter() {
return counter;
}
}
In the org.module.global module, we have added a line to export this package. Basically we’re exposing this package to any other module that requires it.
To confirm this we can fire the below commands on terminal and we will get the listed dependencies as expected.
jdeps --module-path out/production -m org.module.global
java --module-path out/production --describe-module org.module.global
And you can see describe-module describes the dependencies and the directive information. We can see our exports there as well as the mandated requires java.base.
Now we create a second module which would contain some kind of utility code. we will use the transitive modifier here, so that anything that requires org.module.util, will also be able to read org.module.global.
module org.module.util {
requires transitive org.module.global;
}
We now create one interface and one more module to implement this interface. We’re gonna require the module that has our interface that we just added, so we will also add the requires line in the module.
package maxCode.online.module.util;
public interface Countable {
void countMe();
}
module org.module.base {
requires org.module.util;
}
Now what we need to do, we need to actually export the package in org.module.util that has the interface. We need to do that before we can access that. So we will have to add the below line to the org.module.util
exports maxCode.online.module.util
So effectively, the module org.module.util should look like below
module org.module.util {
requires transitive org.module.global;
exports maxCode.online.module.util
}
Next we need to create the class that implements the interface.
package org.pkg.entity;
import maxCode.online.module.ApplicationConstants;
import maxCode.online.module.util.Countable;
public class BaseEntity implements Countable {
// Constructor calls the countMe method
public BaseEntity() {
countMe();
}
// main method creates several objects then prints out how many
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
BaseEntity b1 = new BaseEntity();
}
System.out.println(ApplicationConstants.APP_NAME + " created " +
ApplicationConstants.getCounter() + " objects ");
}
// countMe implements Countable.countMe method
public void countMe() {
ApplicationConstants.addCounter();
}
}
So this class implements the countable interface, as you can see, and that interface, comes from our package, maxCode.online.module.util, but in the module org.module.util, we don’t have a lot there because we’ve got to constructor there. And we can see that we’re also accessing a method enfueled on the application constant’s class on the package maxCode.online.module, in the module org.module.globals. And the main method there, just creates objects and prints out the count from the application constants static fields.
Here we can try out the jdeps commands to examine each module and the relative dependencies.
It’s interesting to note that if your module has dependencies or modules not in java’s standard edition, or the jdk, that you have to specify module path, even if the module path is in the current directory, which it currently is.
Also, in jdeps, -p stands for package and is not synonymous with —module-path, so keep this in mind.
How to identify friends modules
Alright so far we have covered unqualified exports directive, a required directive, as well as the required directive with the transient modifier. What we are going to look at now is the qualified exports directive, identifying friend modules.
We first need to update the org.module.util as below. We have changed the exports directive to a qualified directive, specifying that the package will only be read by the friends we specify after the to keyword.
In this case, I’ve defined the module org.module.base as a friend to the org.pkg.util module. And to get this working, we will have to add the module org.module.concrete to org.module.util
Basically, org.module.Concrete was added to the qualified exports directive. Now you’ll note that the modules defined as friends are listed in a comma delimited list after the to keyword. A package name can only be in a single exports directive but can be exported to multiple friend modules. Also note that attempting to define multiple friends for the same package in different directives is a compile error. The only valid way is one exports directive and a comma separated list of friends as we have done.
module org.module.util {
requires transitive org.module.global;
exports org.pkg.util to org.module.base, org.module.concrete;
}
java
module org.module.concrete {
requires org.module.util;
}
```java
package org.pkg.concrete;
import maxCode.online.module.ApplicationConstants;
import maxCode.online.module.util.Countable;
public class Couple implements Countable {
// Constructor calls the countMe method
public Couple() {
countMe();
}
// implement countMe method from the interface
public void countMe() {
ApplicationConstants.addCounter();
ApplicationConstants.addCounter();
}
}
So that is all for modules section. In the above sections, we saw how to create and declare modules as well as editing the module-info.java files manually, and we’ve used a combination of some of the directives available.
Module Graphs
In the previous sections we already saw a graph for Java SE module. So to create the module graphs for the user created module, first step is to go to whatever output directory you put your projects in.
jdeps -v org.module.global
OR
jdeps -verbose org.module.global
Both will result the same output. It will display dependencies down to the class level, and that could easily become way too much information.
To get a small summary for the module, we can use the below command.
jdeps -s org.module.global
-s or -summary are the same.
Keep this in mind that the alternatives are -verbose and -summary, with a single dash and not the usual double-dash.
The summary here is actually a textual representation of the module graph.
org.module.global -> java.base
So you can interpret a module graph by the way the arrow, called the read edge, is directing you.
org.module.util org.module.global
\ /
\ /
\ /
\ /
\ /
\ /
java.base
org.module.util reads java.base, and org.module.global also reads java.base. Note that org.module.util does not read org.module.global. The read edges correspond to the number of elements listed in the jdeps -s listing.
This to remember for a module graph
- Read the descriptors carefully, looking for qualified exports and transitive dependencies - these actually reduce the dependencies.
- The java.base module is an implicit dependency for every module.
- If you see a module graph with any module that does not read java.base, it’s not an accurate graph.
- If you see a module graph with a node that is a package, that is not a valid module graph.
So this is where is finish the module section. The module has a lot of commands that should be tried and tested to understand them properly.