Java Platform Module System (JPMS)
A module is a named group of packages that states two things explicitly: which other modules it needs, and which of its packages other modules may use. Everything else inside it is hidden. The Java Platform Module System (JPMS), added in Java 9, brought modules to the language, and the JDK itself was split into about 70 of them. A module is described by one small file at its root, module-info.java. In this lesson, you will learn what problems modules solve, how to write a module descriptor, and how to compile and run a small application made of two modules.
What Was Wrong Before Modules
| Problem with the class path | How modules fix it |
|---|---|
No real encapsulation. Any public class in any JAR can be used by anyone, including classes meant to be internal | A module exports only chosen packages; the rest can't be reached, even if public |
Missing JARs are found late. A forgotten dependency shows up as NoClassDefFoundError at run time, maybe days later | Dependencies are declared; a missing module is reported at startup |
| "JAR hell". Two JARs containing the same package silently shadow each other | A package may belong to only one module; duplicates are an error |
| One huge JDK. Every program carried the whole runtime | A program can be shipped with only the modules it needs (see "Jlink in Java: Custom Runtime Images") |
The Module Descriptor
module expenses.core {
exports com.poolstring.expenses.model;
exports com.poolstring.expenses.service;
}| Part | Meaning |
|---|---|
module expenses.core | Declares a module and its name |
exports <package> | Other modules may use the public types of this package |
requires <module> | This module needs another module |
| (not mentioned) | A package that isn't exported is internal to the module |
The file is named module-info.java, sits in the root of the module's source folder, and compiles to module-info.class. Every module automatically requires java.base, which holds java.lang, java.util, java.io and other core packages.
Kinds of Module
| Kind | What it is |
|---|---|
| Named (explicit) module | Has a module-info, placed on the module path |
| Automatic module | An ordinary JAR placed on the module path; it gets a name from its file name and exports everything |
| Unnamed module | All code on the class path; can read every module |
| Platform modules | The JDK's own modules: java.base, java.sql, java.xml, java.net.http and so on |
Example: Looking at Modules from Code
Every class knows its module:
import java.lang.module.ModuleDescriptor;
import java.sql.Connection;
import java.util.List;
public class Modules {
public static void main(String[] args) {
Module base = String.class.getModule();
System.out.println(String.class.getSimpleName() + " is in "
+ base.getName());
System.out.println(Connection.class.getSimpleName() + " is in "
+ Connection.class.getModule().getName());
System.out.println(List.class.getPackageName() + " exported? "
+ base.isExported("java.util"));
System.out.println("jdk.internal.misc exported? "
+ base.isExported("jdk.internal.misc"));
Module mine = Modules.class.getModule();
System.out.println("This class: named module? " + mine.isNamed()
+ ", name: " + mine.getName());
ModuleDescriptor sql = Connection.class.getModule().getDescriptor();
System.out.println("java.sql requires:");
sql.requires().stream()
.map(r -> " " + r.name()
+ (r.modifiers().contains(
ModuleDescriptor.Requires.Modifier.TRANSITIVE)
? " (transitive)" : ""))
.sorted()
.forEach(System.out::println);
System.out.println("java.sql exports:");
sql.exports().stream().map(e -> " " + e.source()).sorted()
.forEach(System.out::println);
long total = ModuleLayer.boot().modules().size();
System.out.println("More than 20 modules loaded at startup? "
+ (total > 20));
}
}String is in java.base
Connection is in java.sql
java.util exported? true
jdk.internal.misc exported? false
This class: named module? false, name: null
java.sql requires:
java.base
java.logging (transitive)
java.transaction.xa (transitive)
java.xml (transitive)
java.sql exports:
java.sql
javax.sql
More than 20 modules loaded at startup? trueExplanation:
Stringlives injava.baseandConnectioninjava.sql.java.utilis exported byjava.base;jdk.internal.miscisn't, so your code can't use it. That is the JDK protecting its internals.- This program runs from the class path, so its class is in the unnamed module: no name, and no
module-info. - The descriptor of
java.sqlshows what it requires and exports. "Transitive" is explained in "Requires, Exports and Opens in Java Modules".
Example: A Two-Module Application
This chapter builds a small expense tracker from two modules:
expenses/
src/
expenses.core/
module-info.java
com/poolstring/expenses/model/Expense.java
com/poolstring/expenses/service/ExpenseTracker.java
com/poolstring/expenses/internal/Rounding.java
expenses.app/
module-info.java
com/poolstring/expenses/app/Main.javaexpenses.core contains the logic and exports two of its three packages. expenses.app contains main and needs the core:
module expenses.app {
requires expenses.core;
}package com.poolstring.expenses.app;
import com.poolstring.expenses.service.ExpenseTracker;
public class Main {
public static void main(String[] args) {
ExpenseTracker tracker = new ExpenseTracker();
tracker.add("Food", "450.50");
tracker.add("Travel", "1200");
tracker.add("Food", "199.25");
System.out.println("By category: " + tracker.byCategory());
System.out.println("Total: " + tracker.total());
System.out.println("Running in module: "
+ Main.class.getModule().getName());
}
}Compile both modules and run the application:
javac -d out --module-source-path src $(find src -name "*.java")
java --module-path out -m expenses.app/com.poolstring.expenses.app.MainBy category: {Food=649.75, Travel=1200}
Total: 1849.75
Running in module: expenses.appExplanation:
--module-source-path srctellsjavacthat each folder undersrcis a module. The output folderoutgets one subfolder per module, each with amodule-info.class.--module-path outtellsjavawhere to find modules (the module path, not the class path).-m module/mainclassnames the module and the class to start.- The JVM checks the module graph at startup:
expenses.apprequiresexpenses.core, which requiresjava.base. If any were missing, the program wouldn't start.
java --describe-module prints what a module declares:
exports com.poolstring.expenses.model
exports com.poolstring.expenses.service
requires java.base mandated
contains com.poolstring.expenses.internalThe internal package is listed as contains: it is in the module but not exported.
Do You Have to Use Modules?
No. The class path still works, and most applications run from it, in the unnamed module. Modules pay off most for libraries that want to hide internals, and for applications that ship their own trimmed-down runtime. Even if you never write a module-info.java, the JDK's own modules affect you: its internal packages are closed, which is why very old libraries that reached into them needed updates.
Common Mistakes
Putting module-info.java in a package folder. It belongs in the root of the module's sources.
Expecting public to mean "usable everywhere". In a module, a public class in a non-exported package is hidden from other modules.
Mixing up the two paths. Modules go on --module-path; putting a modular JAR on the class path makes it behave like an ordinary JAR.
The same package in two modules. This "split package" is an error.
Interview Corner
What is the Java Platform Module System?
JPMS, introduced in Java 9, adds modules: named, self-describing collections of packages. A module's module-info.java declares which modules it requires and which packages it exports; non-exported packages are strongly encapsulated, even their public types. Benefits are reliable configuration (missing or conflicting modules are detected at startup), strong encapsulation of internals, and the ability to build small custom runtime images with jlink. The JDK itself is modularized, with java.base as the module every other module depends on.
What is the difference between a module and a package?
A package groups related classes and controls access through public, protected and package-private. A module groups related packages and adds a higher level of access control: it decides which packages are visible to other modules at all, and states its dependencies on other modules. A JAR on the class path has no such boundary.
Practice
- Easy: Run
java --list-modulesand count the modules whose names start withjava.. - Harder: Create the two-module project above, compile it, and run it. Then run
java --module-path out --describe-module expenses.app.
Key Takeaways
Was this tutorial helpful?