1.2.  Declare modules and enable access between modules

[Note]

A Java platform module consists of:

There are four types of modules in the Java Platform Module System (JPMS):

When creating an appication module, it is important that its name is unique. It is expected that module names will follow the "reverse domain name" convention, just like package names. For example "by.boot.java"

A module name can be the same as package name, however it can be different too, for example java.sql module contains java.sql and javax.sql packages:

C:\Program Files\Java\jdk-11.0.2>java --describe-module java.sql
java.sql@11.0.2
exports java.sql
exports javax.sql
requires java.transaction.xa transitive
requires java.logging transitive
requires java.xml transitive
requires java.base mandated
uses java.sql.Driver
					

To declare an application module, a module declaration needs to be specified. This is done in a special file called module-info.java. As you can see from the file extension, this is a .java file that gets compiled into a .class file by the Java compiler.

module by.boot.java.mod.logger {
}
					

There could be additional directives between curly braces, but all of them are optional.

Next, create a new class for the module:

package by.boot.java.pkg.logger;
public class MyLogger {
    public static void main(String[] args) {
        System.out.println("MyLogger - main() method called");
    }
}
					

[Important]

Module may not have classes with unnamed package (i.e. those which belong to "default" package)

Special module-info.java file must be at the root of Java class packages. Place sources as follows:

C:\1Z0-817
└───mod1
    │   module-info.java
    │
    └───by
        └───boot
            └───java
                └───pkg
                    └───logger
                            MyLogger.java
					

Compile the sources (normally this will be done by IDE for you, but for learning purposes we do it from command line):

C:\1Z0-817\mod1>javac module-info.java
C:\1Z0-817\mod1>javac by/boot/java/pkg/logger/MyLogger.java
					

C:\1Z0-817
└───mod1
    │   module-info.class
    │   module-info.java
    │
    └───by
        └───boot
            └───java
                └───pkg
                    └───logger
                            MyLogger.class
                            MyLogger.java
					

To run a modular application, you specify the module path, which is similar to the class path, but contains modules. You also specify the main class in the format modulename/classname:

C:\1Z0-817>java --module-path ./mod1 --module by.boot.java.mod.logger/by.boot.java.pkg.logger.MyLogger
MyLogger - main() method called
					

Instead of --module-path and --module, you can use the single-letter options -p and -m:

C:\1Z0-817>java -p ./mod1 -m by.boot.java.mod.logger/by.boot.java.pkg.logger.MyLogger
MyLogger - main() method called
					

You can create a modular JAR file using jar utility:

C:\1Z0-817>jar --create --file log.jar --main-class by.boot.java.pkg.logger.MyLogger --module-version 1.0.0 -C mod1 .
					

it creates a modular JAR with the following structure:

C:\1Z0-817>jar -tvf log.jar
     0 Thu Mar 21 23:07:34 AST 2019 META-INF/
   112 Thu Mar 21 23:07:34 AST 2019 META-INF/MANIFEST.MF
   299 Thu Mar 21 23:07:34 AST 2019 module-info.class
     0 Wed Mar 20 23:30:22 AST 2019 by/
     0 Wed Mar 20 23:30:28 AST 2019 by/boot/
     0 Wed Mar 20 23:30:36 AST 2019 by/boot/java/
     0 Wed Mar 20 23:30:40 AST 2019 by/boot/java/pkg/
     0 Wed Mar 20 23:34:54 AST 2019 by/boot/java/pkg/logger/
   465 Wed Mar 20 23:34:54 AST 2019 by/boot/java/pkg/logger/MyLogger.class
   179 Wed Mar 20 23:31:06 AST 2019 by/boot/java/pkg/logger/MyLogger.java
    37 Wed Mar 20 23:24:24 AST 2019 module-info.java
   					

Make a note of META-INF/MANIFEST.MF content:

Manifest-Version: 1.0
Created-By: 11.0.2 (Oracle Corporation)
Main-Class: by.boot.java.pkg.logger.MyLogger
					

You can also use jar utility to check sreucture of the module:

C:\1Z0-817>jar -d -f log.jar
by.boot.java.mod.logger@1.0.0 jar:file:///C:/1Z0-817/log.jar/!module-info.class
requires java.base mandated
contains by.boot.java.pkg.logger
main-class by.boot.java.pkg.logger.MyLogger
					

Finally, you can run the Main-Class from the created JAR:

C:\1Z0-817>java -p ./log.jar -jar log.jar
MyLogger - main() method called
					

If the main class not defined, you can provide it explicitly:

C:\1Z0-817>java -p ./log.jar -m by.boot.java.mod.logger/by.boot.java.pkg.logger.MyLogger
MyLogger - main() method called
					

requires directive

By default our module implicitly requires only java.base which contains basic modules:

C:\1Z0-817>java --describe-module java.base
java.base@11.0.2
exports java.io
exports java.lang
...
exports java.math
exports java.net
exports java.nio
exports java.security
exports java.text
exports java.time
exports java.util
exports java.util.concurrent
exports java.util.concurrent.atomic
exports java.util.concurrent.locks
exports java.util.function
exports java.util.jar
...
					

[Important]

All standard Java SE modules have implicit and mandatory dependency on java.base. You do not need to define dependency on java.base explicitly.

[Important]

It is not allowed to have circular dependencies between modules. If module A requires module B, then module B cannot also require module A.

Imagine you want to use java.util.logging package which resides in the java.logging module. In this case you must explicitly request it. Update the class first:

package by.boot.java.pkg.logger;

import java.util.logging.Logger;

public class MyLogger {
    
    private static final Logger LOG = Logger.getLogger(MyLogger.class.getName());
    
    public static void main(String[] args) {
        System.out.println("MyLogger - main() method called");
    }
    
    public static void log() {
        LOG.info("MyLogger - log() method called");
    }
}
					

recompile the class:

C:\1Z0-817\mod1>javac by/boot/java/pkg/logger/MyLogger.java
					

So far so good. But as soon as you run the class you get an exception:


C:\1Z0-817>java -p ./mod1 -m by.boot.java.mod.logger/by.boot.java.pkg.logger.MyLogger

Exception in thread "main" java.lang.IllegalAccessError: class by.boot.java.pkg.logger.MyLogger (in module by.boot.java.mod.logger) 
cannot access class java.util.logging.Logger (in module java.logging) because module by.boot.java.mod.logger does not read module java.logging
        at by.boot.java.mod.logger/by.boot.java.pkg.logger.MyLogger.<clinit>(MyLogger.java:7)

					
					

in order to fix the problem we must add requires directive to module-info.java, which allows us to declare dependencies:

module by.boot.java.mod.logger {
    requires java.logging;
}
					

Now by.boot.java.mod.logger module has both a runtime and a compile-time dependency on java.logging module. And all public types exported from a dependency are accessible by our module when we use this directive.

Recompile the module declaration module-info.java file and re-run MyLogger class:

C:\1Z0-817\mod1>javac module-info.java
					

C:\1Z0-817\mod1>java -p . -m by.boot.java.mod.logger/by.boot.java.pkg.logger.MyLogger
MyLogger - main() method called
Mar 22, 2019 10:00:29 PM by.boot.java.pkg.logger.MyLogger log
INFO: MyLogger - log() method called
					

requires static directive

You can use requires static to specify that a module dependency is required in the compile time, but optional in the runtime, for example:

module by.boot.java.mod.logger {
    requires static java.logging;
}
					

Static dependencies are useful for frameworks and libraries. Suppose that you are building a library to work with different kinds of databases. The library module can use static dependencies to require different kinds of JDBC drivers. At compile time, the library’s code can access types defined in those drivers. At runtime, users of the library can add only the drivers they want to use. If the dependencies are not static, users of the library have to add all supported drivers to pass the module resolution checks.

exports directive

Assume we create new client module which uses our logger module. We already know that we must declare in module-info.java which modules are required:

module by.boot.java.mod.client {
    requires by.boot.java.mod.logger;
}
					

and a client class:

package by.boot.java.pkg.client;

import by.boot.java.pkg.logger.MyLogger;

public class MyClient {
    public static void main(String[] args) {
        MyLogger.log();
    }
}
					

The project with two modules will look as follows:

C:\1Z0-817
├───mod1
│   │   module-info.class
│   │   module-info.java
│   │
│   └───by
│       └───boot
│           └───java
│               └───pkg
│                   └───logger
│                           MyLogger.class
│                           MyLogger.java
│
└───mod2
    │   module-info.java
    │
    └───by
        └───boot
            └───java
                └───pkg
                    └───client
                            MyClient.java
					

Now compile the new code:

C:\1Z0-817>javac -p ./mod1 mod2/module-info.java
					

As soon as you try to compile MyClient.java it fails:

C:\1Z0-817>javac -p ./mod1 mod2/by/boot/java/pkg/client/MyClient.java
mod2\by\boot\java\pkg\client\MyClient.java:3: error: package by.boot.java.pkg.logger is not visible
import by.boot.java.pkg.logger.MyLogger;
                       ^
  (package by.boot.java.pkg.logger is declared in module by.boot.java.mod.logger, which is not in the module graph)
1 error					
					

[Important]

By default, a module doesn’t expose any of its API to other modules. This strong encapsulation was one of the key motivators for creating the module system in the first place.

As you can see, the by.boot.java.mod.logger module classes are not visible in the by.boot.java.mod.client module, even after we declared it as required. Our code is significantly more secure, but now we need to explicitly open our API up to the world if we want it to be usable.

We need to use the exports directive to expose all public members of the by.boot.java.pkg.logger package:

module by.boot.java.mod.logger {
    requires java.logging;
    exports by.boot.java.pkg.logger;
}
					

[Important]

You define module name with requires directive.

You define package name with exports directive.

[Important]

When you export a package, you only export types in this package but not types in its subpackages.

The same Java package can only be exported by a single Java module at runtime. You cannot have two (or more) modules that export the same package in use at the same time. The JVM will complain at startup if you do. A Java package may not split members (classes, interfaces, enums) between multiple modules.

Re-compile logger module definition:

C:\1Z0-817>javac mod1/module-info.java mod1/by/boot/java/pkg/logger/MyLogger.java
					

[Important]

You may not export empty or non-existent package:

C:\1Z0-817>javac mod1/module-info.java mod1/by/boot/java/pkg/logger/MyLogger.java
mod1\module-info.java:4: error: package is empty or does not exist: aaa
    exports aaa;
            ^
1 error
						

Now you can compile and run client class:

C:\1Z0-817\mod2>javac -p ../mod1; module-info.java by/boot/java/pkg/client/MyClient.java
					

C:\1Z0-817\mod2>java -p ../mod1;. -m by.boot.java.mod.client/by.boot.java.pkg.client.MyClient
Mar 22, 2019 11:59:29 PM by.boot.java.pkg.logger.MyLogger log
INFO: MyLogger - log() method called
					

The project structure looks as follows:

C:\1Z0-817
├───mod1
│   │   module-info.class
│   │   module-info.java
│   │
│   └───by
│       └───boot
│           └───java
│               └───pkg
│                   └───logger
│                           MyLogger.class
│                           MyLogger.java
│
└───mod2
    │   module-info.class
    │   module-info.java
    │
    └───by
        └───boot
            └───java
                └───pkg
                    └───client
                            MyClient.class
                            MyClient.java
					

If you fail to list all modules in the modules path option, you get a java.lang.module.FindException:

C:\1Z0-817\mod2>java -p . -m by.boot.java.mod.client/by.boot.java.pkg.client.MyClient
Error occurred during initialization of boot layer
java.lang.module.FindException: Module by.boot.java.mod.logger not found, required by by.boot.java.mod.client
					

exports...to directive

When you are using exports directive to export a package in the module declaration, this package is visible to all modules that use requires directive to require it. Sometimes you may want to limit the visibility of certain packages to some modules only.

You can restrict which modules have access to your packages by using the exports ... to <module name> syntax:

module by.boot.java.mod.logger {
    requires java.logging;
    exports by.boot.java.pkg.logger to by.boot.java.mod.client;
}
					 

Open modules

In the module declaration, you can add the modifier open before module to declare it as an open module. An open module grants compile time access to explicitly exported packages only, but it grants access to types in all its packages at runtime:

open module by.boot.java.mod.logger {
    requires java.logging;
}
					

Re-compile the by.boot.java.mod.logger module definition and re-run the client:

C:\1Z0-817\mod1>javac module-info.java
					

C:\1Z0-817>java -p ./mod1;./mod2 -m by.boot.java.mod.client/by.boot.java.pkg.client.MyClient
Mar 23, 2019 11:06:33 PM by.boot.java.pkg.logger.MyLogger log
INFO: MyLogger - log() method called
					

NOTE: since we have already compiled MyClient class, we succeeded, because all packages accessible at runtime. However, if we want to re-compile MyClient.java, then exports declaration will be a must (e.g. exports by.boot.java.pkg.logger to by.boot.java.mod.client;).

Before Java 9 (and Java 11), it was possible to use reflection to examine every type and member in a package, even the private ones by using field.setAccessible(true).

The open modifier also grants reflective access to all types in all packages. All types include private types and their private members. If you use the reflection API and suppress Java language access checks using the method AccessibleObject.setAccessible(true) — you can access private types and members in open modules.

Restore by.boot.java.mod.logger module definition as follows:

module by.boot.java.mod.logger {
    requires java.logging;
    exports by.boot.java.pkg.logger to by.boot.java.mod.client;
}
					

and add new AccessLog class in the by.boot.java.mod.client module:

package by.boot.java.pkg.client;

import by.boot.java.pkg.logger.MyLogger;
import java.lang.reflect.Field;

public class AccessLog {
    public static void main(String[] args) throws Exception {
        MyLogger myClass = new MyLogger();
        Field field1 = myClass.getClass().getDeclaredField("LOG"); // private field LOG in MyLogger 
        field1.setAccessible(true);
        System.out.print(field1.get(myClass).getClass().getCanonicalName());
    }
}					
					

Compile both changed sources:

C:\1Z0-817>javac -p ./mod1;./mod2 ./mod1/module-info.java ./mod1/by/boot/java/pkg/logger/MyLogger.java

C:\1Z0-817>javac -p ./mod1; ./mod2/module-info.java ./mod2/by/boot/java/pkg/client/AccessLog.java
					

and run the AccessLog, which attempts to read private field via reflection:

C:\1Z0-817>java -p ./mod1;./mod2 -m by.boot.java.mod.client/by.boot.java.pkg.client.AccessLog

Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make field private static final java.util.logging.Logger 
by.boot.java.pkg.logger.MyLogger.LOG accessible: module by.boot.java.mod.logger does not "opens by.boot.java.pkg.logger" 
to module by.boot.java.mod.client                                                                                                                                 
        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:340)
        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:280)
        at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:176)
        at java.base/java.lang.reflect.Field.setAccessible(Field.java:170)
        at by.boot.java.mod.client/by.boot.java.pkg.client.AccessLog.main(AccessLog.java:10)
					

In order to fix code, make the by.boot.java.mod.logger module open, and in the same time you can remove exports directive, since we do not need to compile AccessLog class:

open module by.boot.java.mod.logger {
    requires java.logging;
}

					

C:\1Z0-817
├───mod1
│   │   module-info.class
│   │   module-info.java
│   │
│   └───by
│       └───boot
│           └───java
│               └───pkg
│                   └───logger
│                           MyLogger.class
│                           MyLogger.java
│
└───mod2
    │   module-info.class
    │   module-info.java
    │
    └───by
        └───boot
            └───java
                └───pkg
                    └───client
                            AccessLog.class
                            AccessLog.java
                            MyClient.class
                            MyClient.java
					

Recompile by.boot.java.mod.logger module definition:

C:\1Z0-817\mod1>javac module-info.java					
					

And re-run AccessLog class:

C:\1Z0-817>java -p ./mod1;./mod2 -m by.boot.java.mod.client/by.boot.java.pkg.client.AccessLog
java.util.logging.Logger
 					

As you can see, open module granted access to private field via reflection API.

opens directive

The opens directive specifies the name of a package to be opened by the current module. This makes public and protected types in the package, and their public and protected members, be accessible to code in other modules at run time only.

It also makes all types in the package, and all their members, be accessible via the reflection libraries of the Java SE Platform.

You can use opens directive to open individual packages to other modules. You can access open packages using the reflection API at runtime. Just like open modules, all types in an open package and all their members (including private)can be reflected by the reflection API:

module by.boot.java.mod.logger {
    requires java.logging;
    exports by.boot.java.pkg.logger to by.boot.java.mod.client;
    opens by.boot.java.pkg.logger;
}
					

You can also qualify open packages using to <module name>:

module by.boot.java.mod.logger {
    requires java.logging;
    exports by.boot.java.pkg.logger to by.boot.java.mod.client;
    opens by.boot.java.pkg.logger to by.boot.java.mod.client;
}
					

requires transitive directive

The module readability relationship is not transitive by default. In our example by.boot.java.mod.logger module can read java.logging module, and by.boot.java.mod.client module can read by.boot.java.mod.logger. But by.boot.java.mod.client cannot read classes from java.logging module.

Let's update code of MyLogger and add getLog method:

public class MyLogger {
	...    
    public static Logger getLog() {
        return LOG;
    }
}
					

Re-compile the code:

C:\1Z0-817>javac -p ./mod1;./mod2 ./mod1/module-info.java ./mod1/by/boot/java/pkg/logger/MyLogger.java
					

Now MyLogger class returns to outer world java.util.logging package's type.

Then update code of MyClient class to get instance of java.util.logging.Logger class:

import java.util.logging.Logger;

public class MyClient {
    public static void main(String[] args) {        
        ...
        Logger l = MyLogger.getLog();
    }
}
					

Try to compile:

C:\1Z0-817>javac -p ./mod1;./mod2 ./mod2/module-info.java ./mod2/by/boot/java/pkg/client/MyClient.java
.\mod2\by\boot\java\pkg\client\MyClient.java:3: error: package by.boot.java.pkg.logger is not visible
import by.boot.java.pkg.logger.MyLogger;
                       ^
  (package by.boot.java.pkg.logger is declared in module by.boot.java.mod.logger, which does not export it)
.\mod2\by\boot\java\pkg\client\MyClient.java:4: error: package java.util.logging is not visible
import java.util.logging.Logger;
                ^
  (package java.util.logging is declared in module java.logging, but module by.boot.java.mod.client does not read it)
2 errors
					

As it shows, java.logging module (and specifically java.util.logging.Logger) is not visible in by.boot.java.mod.client.

It is the same situation we had in the beginning, and it can be resolved by adding requires java.logging; directive to by.boot.java.mod.client module's module-info.java.

This can be a tedious task when many modules depend on each other. Since this is a common usage scenario, Java 11 provides built-in support for it. The requires declaration can be extended to add the modifier transitive to declare the dependency as transitive. The transitive modules that a module depends on are readable by any module that depends upon this module. This is called implicit readability.

In order to fix, edit module-info.java of by.boot.java.mod.logger module adding transitive modifier:

module by.boot.java.mod.logger {
    requires transitive java.logging;
    exports by.boot.java.pkg.logger to by.boot.java.mod.client;
    opens by.boot.java.pkg.logger to by.boot.java.mod.client;
}
					

Now try to compile the code again, and it compiles without errors:

C:\1Z0-817>javac -p ./mod1;./mod2 ./mod1/module-info.java ./mod1/by/boot/java/pkg/logger/MyLogger.java

C:\1Z0-817>javac -p ./mod1;./mod2 ./mod2/module-info.java ./mod2/by/boot/java/pkg/client/MyClient.java
					

Professional hosting         Exam 1Z0-817: Upgrade OCP Java 6, 7 & 8 to Java SE 11 Developer Quiz     Exam 1Z0-810: Upgrade to Java SE 8 Programmer Quiz