Java 9 introduces the Project Jigsaw, which both adds modularization to the platform and modularizes the JDK itself. The goal of Jigsaw is twofold: to enable reliable configuration and add strong encapsulation to Java. With modularization, it is now possible to restrict which packages are public and also ensure that runtime dependencies are present when an application is launched.
The Java Platform Module System (JPMS) is implemented as a separate layer within the JVM. This distinguishes it from other module systems, such as OSGi, which are implemented using Classloaders. JPMS enables modularization of the JDK itself.
Project Jigsaw is made up of a JSR (Java Specification Request) and multiple JEPs (JDK Enhancement Proposals). The specifications that make up Jigsaw are as follows:
JSR 376 Java Platform Module System
JEP 200: Modular JDK
JEP 201: Modular Source Code
JEP 220: Modular Runtime Images
JEP 260: Encapsulate Most Internal APIs
JEP 261: Module System
JEP 282: jlink: Java Linker
The main project page for Jigsaw has links to each of these specifications.
Java modules are a JAR file containing a module-info.java
file in the default package. Since “module-info” is an invalid Java class name, it is ignored by Java 8 and earlier. It does get compiled to bytecode and is available via reflection. The module file declares the name of the module, dependencies of a module, and which packages are exported by this module. Services provided by this module can also be specified in this file.
The module system has the following rules:
Modules specify which packages are exported. Public types within these packages are available to other modules.
Packages not exported are not accessible and Java reflection cannot be used to access types at runtime.
Module names must be globally unique. Reverse domain names should be used as the module name.
Only one version of a module may be loaded. Multiple versions can be loaded using layers (see java.lang.ModuleLayer
).
Module dependency graph cannot contain cycles.
All modules’ dependencies must be present on startup. Any missing dependencies result in an error.
Module path, analogous to the classpath, has been added to Java tools.
Java applications do not need to be modularized to run on Java 9. If the code is loaded using the classpath instead of the module path, the code will run as it did pre-Java 9. If code is loaded using the module path, then the module graph is resolved and dependencies are checked.
With a JAR file, how it is interpreted by the module system is dependent upon whether it is loaded on the module path or classpath and also whether it contains a module-info.java
file. JPMS will create either an application module, unnamed module, or automatic module. Table 21-1 gives the breakdown of the behavior.
--module-path | -classpath | |
---|---|---|
Modular JAR |
Application module |
Unnamed module |
Nonmodular JAR |
Automatic module |
Unnamed module |
JPMS will automatically create a module for JAR files added to the module path which are missing module-info.java
. All packages in an automatic module are exported. The name of the automatic module is derived from the name of the JAR file. The rules for the module name are as follows:
“.jar” suffix is removed.
The module name will be extracted from the text preceding the hyphen of the first occurrence of the regular expressions -(\d
(\.|$))+, and the version will be extracted after the hyphen if it can be parsed.
All nonalphanumeric characters will be replaced with a dot, all repeating dots are replaced by a single dot, and all leading/trailing dots are removed.
Table 21-2 shows some examples of module names.
JAR name | Module name | Version |
---|---|---|
forex-calc.jar |
forex.calc |
None |
forex-calc-0.1.0.jar |
forex.calc |
0.1.0 |
forex-0.1.0.jar |
forex |
0.1.0 |
Modules add a new layer of encapsulation to Java. With modules, it is possible to restrict access to public types and selectively declare which modules can access a package. A public class is no longer globally public. A public class can be limited to just its module, or it can be exported and publically accessible to other modules.
The javac
compiler command has been extended with additional parameters for handling modules. Multiple modules may be compiled simultaneously. For multiple modules, each module’s content should be placed in a directory with the same name of the module. An example of this is shown in Figure 21-1.
The example in Figure 21-1 would be compiled with the following command line on a Unix system:
javac -d out --module-source-path src $(find . -name "*.java")
Other module command-line options:
--add-modules <module>(,<module>)*
Root modules to resolve in addition to the initial modules, or all modules on the module path if <module> is ALL-MODULE-PATH
--limit-modules <module>(,<module>)*
Limit the universe of observable modules
--module <module-name>, -m <module-name>
Compile only the specified module, check timestamps
--module-path <path>, -p <path>
Specify where to find application modules
--module-source-path <module-source-path>
Specify where to find input source files for multiple modules
--module-version <version>
Specify version of modules that are being compiled
--upgrade-module-path <path>
Override location of upgradeable modules
As a part of Project Jigsaw (JEP 200), the JDK itself was heavily refactored and modularized. The module java.base
is present by default, meaning you do not need to explicitly require it. However, if you are using JavaFX, JDBC, etc., you will need to include the relevant modules in addition to adding the relevant imports to your code. See Table 21-3 for a list of modules.
The Java compiler uses the java.se.ee
module, whereas at runtime Java uses the java.se
module. The functionality contained in java.se.ee
is typically provided by a Java EE container.
Module | Requires |
---|---|
java.activation |
java.base, java.datatransfer, java.logging |
java.base |
|
java.compiler |
java.base |
java.cobra |
java.base, java.desktop, java.logging, java.naming, java.rmi, java.transaction, jdk.unsupported |
java.datatransfer |
java.base |
java.desktop |
java.base, java.datatransfer, java.prefs, java.xml |
java.instrument |
java.base |
java.logging |
java.base |
java.management |
java.base |
java.management.rmi |
java.base, java.management, java.naming, java.rmi |
java.naming |
java.base, java.security.sasl |
java.prefs |
java.base, java.xml |
java.rmi |
java.base, java.logging |
java.scripting |
java.base |
java.se |
java.base, java.compiler, java.datatransfer, java.desktop, java.instrument, java.logging, java.management, java.management.rmi, java.naming, java.prefs, java.rmi, java.scripting, java.security.jgss, java.security.sasl, java.sql, java.sql.rowset, java.xml, java.xml.crypto |
java.se.ee |
java.activation, java.base, java.corba, java.se, java.transaction, java.xml.bind, java.xml.ws, java.xml.ws.annotation |
java.security.jgss |
java.base, java.naming |
java.security.sasl |
java.base, java.logging |
java.smartcardio |
java.base |
java.sql |
java.base, java.logging, java.xml |
java.sql.rowset |
java.base, java.logging, java.naming, java.sql |
java.transaction |
java.base,java.rmi |
java.xml |
java.base |
java.xml.bind |
java.activation, java.base, java.compiler, java.desktop, java.logging, java.xml, jdk.unsupported |
java.xml.crypto |
java.base, java.logging, java.xml |
java.xml.ws |
java.activation, java.base, java.desktop, java.logging, java.management, java.xml, java.xml.bind, java.xml.ws.annotation, jdk.httpserver, jdk.unsupported |
java.xml.ws.annotation |
java.base |
To prepare for Project Jigsaw in Java 9, Oracle added the jdeps command-line tool in Java 8. This is a static dependency checker, which is meant to aid in preparation for Java 9. This tool has three primary uses:
Identify which JDK modules are required for a set of classes.
Trace transitive dependencies of a set of classes.
Identify dependencies on undocumented internal JDK classes.
The utility can generate the analysis on the console or dump it to a .dot file. Tools such as graphviz can use the .dot file to render the output graphically.
jdeps postgresql-42.1.1.jar
postgresql
-
42.1
.
1
.
jar
->
/
Library
/
Java
/
JavaVirtualMachines
/
jdk1
.
8.0
_131
.
jdk
/
Contents
/
Home
/
jre
/
lib
/
jce
.
jar
postgresql
-
42.1
.
1
.
jar
->
not
found
postgresql
-
42.1
.
1
.
jar
->
/
Library
/
Java
/
JavaVirtualMachines
/
jdk1
.
8.0
_131
.
jdk
/
Contents
/
Home
/
jre
/
lib
/
rt
.
jar
org
.
postgresql
(
postgresql
-
42.1
.
1
.
jar
)
->
java
.
io
->
java
.
lang
->
java
.
net
->
java
.
security
->
java
.
sql
->
java
.
util
->
java
.
util
.
logging
->
org
.
postgresql
.
copy
postgresql
-
42.1
.
1
.
jar
->
org
.
postgresql
.
fastpath
postgresql
-
42.1
.
1
.
jar
->
org
.
postgresql
.
jdbc
postgresql
-
42.1
.
1
.
jar
->
org
.
postgresql
.
largeobject
postgresql
-
42.1
.
1
.
jar
->
org
.
postgresql
.
replication
postgresql
-
42.1
.
1
.
jar
->
org
.
postgresql
.
util
postgresql
-
42.1
.
1
.
jar
…
To identify dependencies on undocumented JDK classes, use –jdkinternals
. Undocumented JDK classes are those that begin with com.sun.*
or sun.*
. These were not meant to be used outside of the JDK and may be removed at any point. With Java 9, many of these APIs have been refactored or removed.
The following jdeps command invocation returns the dependencies for the MyEncoder jar:
jdeps -jdkinternals MyEncoder.jar
MyEncoder
.
jar
->
/
Library
/
Java
/
JavaVirtualMachines
/
jdk1
.
8.0
_131
.
jdk
/
Contents
/
Home
/
jre
/
lib
/
rt
.
jar
org
.
ctjava
.
util
.
TransmitUtil
(
MyEncoder
.
jar
)
->
sun
.
misc
.
BASE64Encoder
JDK
internal
API
(
rt
.
jar
)
Warning:
JDK
internal
APIs
are
unsupported
and
private
to
JDK
implementation
that
are
subject
to
be
removed
or
changed
incompatibly
and
could
break
your
application
.
Please
modify
your
code
to
eliminate
dependency
on
any
JDK
internal
APIs
.
For
the
most
recent
update
on
JDK
internal
API
replacements
,
please
check:
https:
//wiki.openjdk.java.net/display/JDK8/Java+Dependency+Analysis+Tool
JDK
Internal
API
Suggested
Replacement
----------------
---------------------
sun
.
misc
.
BASE64Encoder
Use
java
.
util
.
Base64
@since
1.8
In this example, the class TransmitUtil depends upon an undocumented JDK class which isn’t supported in Java 9.
To define a module, a module-info.java
file must be created in the default package. The content of the file is as follows:
<
open
>
module
<
module
-
name
>
{
[
export
<
java
package
>
[
to
<
module
name
>]
[
requires
[
transitive
]
<
module
-
name
>]
[
opens
<
module
name
>
[
to
<
module
name
]]
[
provides
<
interface
>
with
<
implementation
>]
[
uses
<
interface
>]
}
The module name must be unique and should use the reverse domain name pattern. The following example defines module with the name org.ctjava.admin
, which doesn’t export any packages or depend upon any modules:
module
org
.
ctjava
.
admin
{
}
All public types in a package may be exported by adding an export statement to a module definition:
module
org
.
ctjava
.
admin
{
exports
org
.
ctjava
.
admin
.
api
}
In this example, all public classes in org.ctjava.admin.api
are exported and available to other modules that depend upon org.ctjava.admin
.
Packages can be selectively exported to a specific module, for example:
module
org
.
ctjava
.
admin
{
exports
org
.
ctjava
.
admin
.
ui
to
javafx
.
graphics
;
}
In this example, org.ctjava.admin.ui
is selectively exported to javafx.graphics
. This gives classes in javafx.graphics
access to classes within org.ctjava.admin.ui
without this packaging having to declare a dependency in its module file. This is typically done when another package uses reflection. Without the export, classes in javafx.graphics
would not be able to perform reflection on classes in org.ctjava.admin.ui
.
To declare a dependency upon another package, add the requires
statement to the module definition. The following example has three dependencies, which must be present when the module is loaded at runtime:
module
org
.
ctjava
.
admin
{
requires
javafx
.
controls
;
requires
org
.
ctjava
.
services
;
requires
org
.
ctjava
.
message
.
api
;
}
If a module is exporting a package which uses classes from another module and which will be required by downstream dependencies, then the dependency must include the transitive
keyword.
Consider the following module definition:
module
org
.
ctjava
.
services
{
requires
transitive
org
.
ctjava
.
model
;
exports
org
.
ctjava
.
services
;
}
This definition will export the org.ctjava.services
package, which contains the following class:
public
interface
MeetingService
{
void
scheduleMeeting
(
Meeting
meeting
,
Date
scheduledDate
);
void
updateMeeting
(
Meeting
meeting
);
…
}
}
This class uses the type Meeting
from the org.ctjava.model
package. If org.ctjava.services
didn’t include the transitive
keyword on the requires
for org.ctjava.model
, then users who depend upon org.ctjava.services
would not have access to the Meeting
class and hence could not invoke/use the MeetingService
class.
Modules can be defined that export a service which can be dynamically added to the module path on startup. The Service Provider API was first added in Java 6 and has been modified for Java 9. With Service Providers, you have the following:
A module containing interfaces for a service
One or more modules containing the implementation of the service
A module that uses the service
Figure 21-1 shows an example service implementation. The modules are as follows:
org.ctjava.message.api
::Contains an interface MessageService.java
org.ctjava.email
::Contains an implementation of MessageService.java
org.ctjava.admin
::Uses implementations of org.ctjava.message.api
added to the module path.
The module org.ctjava.message.api
, contains MessageService.java
defining the contract for the service:
module
org
.
ctjava
.
message
.
api
{
exports
org
.
ctjava
.
message
.
api
;
}
The package org.ctjava.message.api
contains an interface defining the service:
package
org
.
ctjava
.
message
.
api
;
public
interface
MessageService
{
void
sendMessage
(
String
memeber
,
String
message
);
}
Providing an implementation of a service is straightforward. The module file declares a dependency on service API and specifies that it provides an implementation of the interface. The new requires
and provides
keywords are needed in the relative statements to declare the dependency as shown here:
module
org
.
ctjava
.
{
requires
org
.
ctjava
.
message
.
api
;
provides
org
.
ctjava
.
message
.
api
.
MessageService
with
org
.
ctjava
.
.
EmailMessageService
;
}
The implementation of the service is straightforward:
package
org
.
ctjava
.
;
import
org.ctjava.message.api.MessageService
;
public
class
EmailMessageService
implements
MessageService
{
@Override
public
void
sendMessage
(
String
memeber
,
String
message
)
{
// send message
}
}
To use a service, a dependency upon the API is declared, along with a uses
statement specifying the interface of the service, as shown here:
module
org
.
ctjava
.
admin
{
requires
org
.
ctjava
.
message
.
api
;
uses
org
.
ctjava
.
message
.
api
.
MessageService
;
}
To use the service, use java.util.ServiceLoader
to get a reference:
Iterable
<
MessageService
>
mservice
=
ServiceLoader
.
load
(
MessageService
.
class
);
for
(
MessageService
ms
:
mservice
)
{
ms
.
sendMessage
(
member
,
"Hello World!"
);
}
The jlink tool assembles and optimizes a set of modules and their dependencies into a custom runtime image. This utility is defined in JEP 282. Only required modules are included in the image. For example, a desktop application using JavaFX is only 95 MB versus 454 MB for the full JDK.
The command jlink
has the following parameters:
jlink --module-path <modulepath> + --add-modules <modules> + --limit-modules <modules> + --output <path>
jlink --module-path $JAVA_HOME/jmods:dist --add-modules org.ctjava.TimerUtil --output test