Java Modules

Last update: 2026-02-19 04:54:04 +0800 +0800
tags: Java | Jpms | Modules
Available translations: 日本語 / 中文(简体) / 中文(繁體)

The Java Platform Module System (JPMS) makes dependencies and package encapsulation explicit. A named module contains a descriptor, module-info.java, at its source root. Compiling it produces module-info.class in an exploded module directory or modular JAR.

java
module com.example.app {
    requires com.example.codec;
    uses com.example.codec.Decoder;
}

module com.example.codec {
    exports com.example.codec.api;
    exports com.example.codec.internal to com.example.app;
    opens com.example.codec.model to com.example.json;
    provides com.example.codec.Decoder with com.example.codec.JsonDecoder;
}

Compilation, Packaging, and Launch #

A conventional multi-module source tree has one directory per module below the module source path. Compile with:

sh
javac --module-source-path src -d out -m com.example.app,com.example.codec
jar --create --file app.jar --main-class com.example.app.Main -C out/com.example.app .
java --module-path libs:app.jar --module com.example.app

jar --main-class records an executable main class, but it is optional because java -m module/name.Main can name one at launch. jlink creates a runtime image from a transitive closure of modules; it accepts modular JARs and JMODs on its module path, while a JMOD is not a runtime artifact.

sh
jlink --module-path "$JAVA_HOME/jmods:libs" \
      --add-modules com.example.app --output image
image/bin/java -m com.example.app

Readability and Encapsulation #

requires makes a module readable. requires transitive re-exports that readability to dependents; requires static is mandatory at compile time but optional at run time. exports grants compile-time and ordinary runtime access to public types in a package, either to all reading modules or with to to a named list. opens grants deep reflection without granting ordinary type access; an open module opens all its packages. Prefer qualified opens for framework integration.

uses and provides ... with ... declare services in module descriptors. ServiceLoader uses those declarations during module resolution. A package is not a unit of access independent of its module, so public alone never bypasses an unexported package.

Resolution and Migration #

For each phase, the module path plus system and upgrade modules form the set of observable modules. Resolution starts from root modules and follows requires edges; duplicate module names fail rather than allowing classpath ordering to choose one. Named modules cannot contain split packages: the same package in two resolved named modules is an error. This rule makes package ownership and encapsulation deterministic.

Classpath code belongs to an unnamed module. It reads exported packages of resolved named modules, and its packages are exported and opened, but a named module cannot declare requires on the unnamed module. A non-modular JAR on the module path becomes an automatic module: it has a derived or manifest name, reads all resolved modules, and exports and opens all packages. Automatic modules help staged migration, but they do not provide meaningful encapsulation; replace them with explicit descriptors before relying on module boundaries.

Reflection across a named-module boundary requires ordinary accessibility plus an open package for deep access. --add-exports and --add-opens are migration or deployment overrides, not a substitute for a descriptor owned by the module.

References #

Comments

Read or comment on GitHub (sign-in required to comment)