W3docs

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 public class 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 NoClassDefFoundError blew 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:

ConceptWhat it groupsVisibility rule
Packageclassespublic/protected/package-private within the JAR
JARpackages + resourceseverything public is visible on the classpath
Modulepackagesonly 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.

java— editable, runs on the server

What to take from the run:

  • String, ArrayList, and HttpClient reported java.base, java.base, and java.net.http. Every class belongs to exactly one module, and getModule() tells you which — the language types all live in java.base, while HttpClient is in its own module you would have to requires java.net.http to use.
  • The program's own class reported isNamed() == false and a getName() of null. 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 the java.* ones shows the JDK really is split into many modules, not one monolith.
  • java.base is not open (isOpen() == false) yet exports many packages; it exposes java.lang and java.util to everyone while keeping jdk.internal.* hidden. Exporting a package and opening a module are different switches — a later chapter returns to opens.
  • 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/provides and ServiceLoader.
  • 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.

Practice

Practice
A library JAR contains a 'public' class in package 'com.acme.internal' that the authors intend only for use inside the library. The JAR is built as a modular JAR whose 'module-info.java' exports 'com.acme.api' but not 'com.acme.internal'. What happens when this JAR is placed on the MODULE PATH and another module tries to import 'com.acme.internal.Helper'?
A library JAR contains a 'public' class in package 'com.acme.internal' that the authors intend only for use inside the library. The JAR is built as a modular JAR whose 'module-info.java' exports 'com.acme.api' but not 'com.acme.internal'. What happens when this JAR is placed on the MODULE PATH and another module tries to import 'com.acme.internal.Helper'?
Was this page helpful?