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.