Discover the basic mechanisms, tools, and rules that you can use to leverage the modular capabilities of Java 9+ and the JPMS to write more clean and well-architected code, libraries, and systems
This content is no longer being updated or maintained. The content is provided “as is.” Given the rapid evolution of technology, some content, steps, or illustrations may have changed.
To use the Java module system, first, you need to understand it. In this tutorial, I introduce you to:
The basic definition of a module, its contents, and configuration
How encapsulation works in Java 9
How the interfaces are defined
How to list the available modules
A comparison of Java 8 and 9 applications
The basic rules for how to use a module
The difference between classpath and modulepath
Now, let's meet the new first-class citizen of the Java language, the module.
Defining the Java 9 module
A module is a collection of code, data, and resources. It is a set of related packages and types (classes, abstract classes, interfaces, and more) with code, data files, and some static resources.
For example, the module descriptor module-info.java is one of the resources in a Java 9 module. (A module descriptor is a compiled version of a module declaration. When you create this file, you have to know two bits of information: what the module will depend on and what it will export.)
Each module contains only a set of related code and data to support the Single Responsibility Principle (SRP): "There should never be more than one reason for a class to change." (The idea is to design a class that has one responsibility.) In more simple terms: module = code + data.
The main goal of the Java 9 module system is to support modular programming in Java.
So, by now Java has many first-class citizens, these language attributes:
The package and object in OOP (that supports basic object-oriented programming) in Java SE 1.0; the package was introduced to organize Java types
Lambda expressions in FP (that supports functional programming) in Java SE 8
The module of the Module System (that supports modular programming) in Java SE 9; related packages were grouped under modules and modules replaced packages to become the basic unit of reuse
Now, let's explore the base module configuration and dependencies and then touch on the mechanisms of a module that enhance encapsulation.
The base module and reliable configuration
Currently, the Java 9 module system has about 98 modules, but it is continuing to evolve. Oracle has categorized JDK jars and Java SE specifications into two sets of modules.
The default module for all JDK and user-defined modules is the base module java.base. It is an independent module and doesn't depend on any other modules. java.base is known as the "mother of Java 9 modules."
In the following image, you can see the modular aspects of the system and can probably make the leap to understand how a modularized JDK means that you can also modularize your own applications.
This is only a few of the 98 platform modules.
The diagram is a dependency graph. Each box indicates a module, and if a module has an arrow pointing to another module, it means that the module where the arrow originates needs the module it is pointing at to perform its functions.
As you can see in the image, java.base is the module that every other module depends on. This is because it contains such foundational classes as Java objects, Integer, String, and so on. All the classes that code cannot work without.
I can make the dependency more explicit:
The diagram gets quite messy, so typically you omit the dependencies from modules to java.base. But it is important to remember that the dependency to java.base is always there.
In the following diagram, the java.logging and the java.xml modules still have an explicit arrow in the diagram. This is to indicate that java.base is the only dependency of a module, which is the case for java.logging and java.xml.
You can see a module that has dependencies other than java.base.
The java.sql module uses the java.logging module (which makes sense because it may have some internal logging to do) and the java.xml module (which might be a bit more surprising, but it's there to handle the XML capabilities of some databases).
By examining the modules and their explicit dependencies in this diagram, you probably already know much, much more about what is in a JDK than you would when looking at the rt.jar file like you would do in previous versions of the language.
You can get a clear picture how different functionalities are packaged in the JDK, and it's obvious where the dependencies between the different modules offering a functionality exists.
Having this information explicitly encoded into modules (with the dependencies) makes it much easier to create reliable applications, especially if you apply these same principles to your application development. Your new, more modular applications will be as easy to understand as this diagram.
Towards better encapsulation and well-defined interfaces
Having explicit dependencies is one of the primary foundations for modularity, but you also need a stronger form of encapsulation and well-defined interfaces, so let's zoom in on a single module and see how the JPMS handles these issues.
You are looking at a single module, the mother module java.base, and as you can see, it has two sections:
The upper half lists packages. In this case, java.lang, java.util, java.io (but there are many more in the real java.base module). These packages are all part of the public interface of this module. Every module depending on java.base can see everything in these packages.
Below the line in the shield part of the module, you see different packages with names like sun.util and jdk.internal. This is a signal that those packages are internal implementation details and other modules that depend on java.base will not be able to access anything in those packages.
This is an example of the strong encapsulation, a crucial security element, that the module system offers.
Listing the JDK's modules
An important organizational aspect of Java 9 is dividing the JDK into modules to support various configurations as is outlined in JEP 200. To list the modules, you use the java command from the JDK's bin folder with the --list-modules option:
The standard modules that implement the Java SE specification (names starting with java.*)
JavaFX modules (names starting with javafx.*)
JDK-specific modules (names starting with jdk.*)
Oracle-specific modules (names starting with oracle.*)
Each module name is followed by a version string. In this case, I am using JDK 9.0.4 version, so each module is followed by the version string @9.0.4.
As an aside, this list can help you investigate the decisions made to introduce modularity into Java 9 by following the evolution of Java modularity in JEPs and JSRs:
You have already developed many Java applications using versions 5, 6, 7, and 8, so you probably have a pretty good idea what a pre-9 Java app looks like and the components it contains. For those who need a refresher, a Java SE 8 application:
And a Java 9 application:
In Java 8 and earlier applications, the top-level component is the package. It places a set of related types into a group. It also contains a set of resources.
The Java 9 application doesn't differ too much from Java 8; it introduces a new component, the module, which is used to place a set of related packages into a group. And also introduces one other new component: the module descriptor module-info.java. (I'll discuss this in the next section.)
Whereas Java 8 applications have packages as a top-level component, Java 9 applications have the module as the top-level component.
By the way, each Java 9 module can be only one module with one module descriptor. Unlike Java 8 packages, you cannot build multiple modules into a single module.
Here is a good list of the main components in a Java 9 module:
One module
Module name
Module descriptor
Set of packages
Set of types and resources
Resources can be the module descriptor or any other properties or XML.
Next, let's dive into the module and module descriptor.
Module and module descriptor basics
Now we are going to discuss two more important concepts of module and module descriptor basics: syntax and rules.
Module basics and rules
You should remember the following important basic rules when developing any Java 9 modules:
Each module has a unique name
Each module has some description in a source file
The module descriptor file is placed in the top-level directory
Each module can have any number of packages and types
One module can depend on any number of modules
Each module has a unique name
Because modules live in a global space in the JVM, each module should have a unique name. Like package and JAR file names, you can use the Reverse Domain Name pattern to define a module name.
For example, if you are going to develop modules for the http://www.taman.com.eg domain, then you can use eg.com.taman.mod1 as your first module name, eg.com.taman.mod2 as the second module name, and so on.
Each module has some description in a source file
Module description is expressed in a source file called module-info.java and should be named like this exactly. Each module should have one and only one module descriptor (module-info.java).
A module descriptor is a Java file. It is not an XML, text, or properties file.
The module descriptor file is placed in the top-level directory
The top-level directory is the root folder of the module.
For example, if you are going to develop the eg.com.taman.mod1 module, then you should place your module descriptor under the eg.com.taman.mod1 module directory.
Each module can have any number of packages and types
One module can depend on any number of modules.
Now, let's see what's in the module descriptor.
The module descriptor
In a Java 9 module, the module descriptor is a resource that contains module metadata that describes the module. It is not an XML or a properties file; it is a plain Java file.
You must name this file module-info.java and place it in the root folder of the module. Like other Java source files, a module file is compiled into module-info.class using the javac command.
A module descriptor is created using the module keyword:
module {
// Module Meta Data goes here.
}
Show more
For example:
module eg.com.taman.mod1 {
}
Show more
This is a simple and minimal module descriptor example. Let's discuss the module metadata.
Module metadata
A module contains the following basic metadata:
A unique name
An exports clause
A requires clause
I'll go into more depth in the following sections and provide some examples.
A unique name
The module has a unique name. You use the module keyword to define the module type as in this example:
module eg.com.taman.mod1 {
}
Show more
An exports clause
A module can export its packages to the outside world so that other modules can use them. In the module descriptor, you use the exports clause to export packages to the outside world or to other modules:
Please note that it is not mandatory to export all packages. It's up to you to decide which ones to export.
A requires clause
A module can import or use other modules packages. In the module descriptor, you use the requires clause to import other modules in order to use their packages:
As you can see in this example, eg.com.taman.mod1 has exported the eg.com.taman.service package, so eg.com.taman.mod2 requires mod1 to import all of its exported packages to use them in its subtypes (of classes, enums, interfaces, etc.).
But remember, the exports keyword exports packages to other modules and the requires keyword imports modules in order to use all their exported packages internally. Any packages defined in a module and not exported are privately encapsulated and can never be accessed.
A module can have more than just this amount of metadata, but this is enough to allow you to begin modular programming. I will discuss how to approach modular programming in the next two tutorials in this series:
Now, let's identify some important points about the module descriptor syntax.
Things to remember about the module descriptor
You should remember these important points before building a module descriptor:
A module descriptor can consist of just the module name and nothing else; no exports or requires clauses.
A module descriptor can consist of one or more exports clauses without a requires clause; this means it is exporting packages to other modules but not depending on any other modules — it's an independent module.
A module descriptor can have both exports and requires clauses; this means it is exporting packages to other modules and using other modules' packages — because it is depending on other modules, it is not an independent module.
A module descriptor can have zero, one, or more requires clauses.
Modules are loaded from the modulepath (just like classes are loaded from a classpath).
Wait, you say. Why can't I just use classpath like before?
Why modulepath?
As a Java developer, you know what classpath Hell is: Similar to DLL Hell from Windows® programming, classpath Hell in Java occurs because your program isn't a fixed grouping of code but instead is the exact set of classes loaded by a JVM in a particular instance. Your code can be in a situation in which the same command line on a platform results in divergent behaviors because of resolution rules. The directory structure might be different. The versions of the standard libraries can be different or hidden. Because Java supports a first-encountered policy, unknown ordering dependencies can really foul up your code.
From Java 9 onwards, you are about to jump into another kind of Hell: modulepath Hell.
A classpath is a sequence of classes and packages or JARs that are user defined and built in. The JVM or Java compiler requires the classpath to compile the application or classes.
Before Java 9, the compiler and runtime located types via the classpath: a list of folders and library archive files containing compiled Java classes. It was defined by a combination of a CLASSPATH environment variable, extensions placed in a special folder of the JRE, and options provided to the javac and java commands. The goal was to decrease the application startup time.
Because types could be loaded from several different locations, the order in which those locations were searched resulted in brittle apps.
Many years ago, I installed a Java app from a third-party vendor on my system. The app's installer placed an old version of a Java library into the JRE's extensions folder. Several Java apps on my system depended on a newer version of that library because it carried additional types and enhanced versions of the library's older types.
Because classes in the JRE's extensions folder were loaded before other classes on the classpath, the apps that depended on the newer library version stopped working, failing at runtime with NoClassDefFoundErrors and NoSuchMethodErrors, sometimes long after the apps began executing.
A modulepath is a sequence of modules (which are provided in a folder or JAR format). If a module is in folder format, it means the module is in Exploded Module format. If it's in a JAR format, that JAR is known as a Modular JAR.
The reliable configuration provided by modules and module descriptors helps eliminate many such runtime classpath problems. Every module explicitly states its dependencies and these are resolved as an application launch.
One module, one package
The modulepath can contain only one of each module and every package can be defined in only one module. If two or more modules have the same name or export the same packages, the runtime immediately terminates before running the program.
Summary
In this tutorial, you learned the basic definition of a module, its contents and configuration; how encapsulation works in Java 9 and how the interfaces are defined; how to list the available modules; the difference between Java 8 and 9 applications; the basic rules for how to use a module; and the difference between classpath and modulepath (and the kinds of hell they can generate).
About cookies on this siteOur websites require some cookies to function properly (required). In addition, other cookies may be used with your consent to analyze site usage, improve the user experience and for advertising.For more information, please review your cookie preferences options. By visiting our website, you agree to our processing of information as described in IBM’sprivacy statement. To provide a smooth navigation, your cookie preferences will be shared across the IBM web domains listed here.