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.
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:
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.appjar --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.
jlink --module-path "$JAVA_HOME/jmods:libs" \
--add-modules com.example.app --output image
image/bin/java -m com.example.appReadability 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.
Comments
Read or comment on GitHub (sign-in required to comment)