Java Modules (JPMS) Introduction
What modules are in Java, the problems JPMS solves, and how it relates to the classpath.
The Java Platform Module System (JPMS), introduced in Java 9, adds a layer above packages. A module is a named, self-describing group of packages that declares two things explicitly: what it needs from other modules, and what it offers to them. That declaration lives in a single file, module-info.java, at the root of the module. This part of the book walks through it; this chapter explains why it exists.
The problem JPMS solves: the classpath
Before Java 9, every JAR was thrown onto one flat classpath. That arrangement had chronic problems:
- No encapsulation. Every
publicclass in every JAR was reachable by everyone. A class meant only as an internal helper (sun.misc.Unsafe,com.example.internal.*) could be used by anyone, so it could never be changed safely. - No declared dependencies. A JAR never said which other JARs it required. You discovered a missing dependency only when a
NoClassDefFoundErrorblew up at runtime — possibly in production. - JAR hell. Two JARs supplying the same package, or two versions of the same library, were silently merged in classpath order. Whichever class loaded first won.
Modules attack all three: a module hides every package it does not explicitly export, declares every module it requires, and the JVM verifies the whole graph at startup — missing or duplicated modules fail fast.
Modules vs. packages vs. JARs
These three are easy to confuse:
| Concept | What it groups | Visibility rule |
|---|---|---|
| Package | classes | public/protected/package-private within the JAR |
| JAR | packages + resources | everything public is visible on the classpath |
| Module | packages | only exported packages are visible to other modules |
A module is usually packaged as a JAR (a "modular JAR" — an ordinary JAR with a module-info.class at its root). The difference is the descriptor: drop the JAR on the classpath and the rules are ignored; put it on the module path and JPMS enforces them.
Strong encapsulation, in one sentence
The headline rule: a package is invisible to other modules unless its module exports it — even if its classes are public. public now means "accessible to code that can read this package," and reading a package requires an exports plus a requires. This is why the JDK itself could finally hide its internals: java.base exports java.util but not jdk.internal.misc.
The JDK is modular too
Since Java 9 the JDK is split into ~70 modules (java.base, java.sql, java.xml, java.net.http, …). java.base is special: it is implicitly required by every module and contains the language essentials (java.lang, java.util, java.io). Every class you have ever used lives in one of these modules — which the worked example below makes visible.
A worked example: inspecting modules at runtime
You do not need to write a module to see modules: the runtime Module API reports the module of any class. This program asks several classes which module they belong to, checks its own module, and peeks at the boot layer the JVM started with.
What to take from the run:
String,ArrayList, andHttpClientreportedjava.base,java.base, andjava.net.http. Every class belongs to exactly one module, andgetModule()tells you which — the language types all live injava.base, whileHttpClientis in its own module you would have torequires java.net.httpto use.- The program's own class reported
isNamed() == falseand agetName()ofnull. Code run from the classpath lands in the unnamed module, a compatibility bucket that requires nothing explicitly and reads every other module. That is why classpath programs still compile and run unchanged on Java 9+. ModuleLayer.boot()exposed the graph of modules the JVM resolved at startup — counting thejava.*ones shows the JDK really is split into many modules, not one monolith.java.baseis not open (isOpen() == false) yet exports many packages; it exposesjava.langandjava.utilto everyone while keepingjdk.internal.*hidden. Exporting a package and opening a module are different switches — a later chapter returns toopens.- Nothing here required a
module-info.java. The Module API is reflective metadata available to any program; writing your own module (next chapter) is what lets you declare these rules rather than just observe the JDK's.
What the rest of this part covers
module-info.java— the directives:requires,exports,opens,uses,provides.- Module types — named, automatic, and unnamed modules and how they mix.
- Services — decoupling an interface from its implementation with
uses/providesandServiceLoader. - Migration — moving an existing classpath application onto the module path without a big-bang rewrite.
Modules are optional: a Java application can run forever on the classpath. But understanding them explains the modern JDK, unlocks jlink custom runtimes, and gives libraries real encapsulation. The next chapter, the module-info.java declaration, writes the descriptor that makes a module a module.