The module system (JPMS) in Java
module-info.java, requires, exports, opens, strong encapsulation.
public. Another team's code can't use it, and the compiler says it's not accessible. Since when is public not public?module-info.java
Since Java 9, a module is a named set of packages described by **module-info.java. It declares what it needs and what it shares. The JDK itself is split into modules such as java.base**.
module com.shop.orders {
requires java.sql;
requires transitive com.shop.model;
exports com.shop.orders.api;
opens com.shop.orders.entity;
}The four directives
**requires: this module depends on another. requires transitive: consumers of this module get that dependency too. exports: a package's public types are usable at compile and run time. opens: the package allows deep reflection** on private members at runtime.
Which module am I in?
What does this print?
class Mine { }
void main() {
Module a = List.class.getModule();
Module b = Mine.class.getModule();
System.out.println(a.getName());
System.out.println(b.getName());
}java.base nulljava.util nulljava.base java.base
Show the answer
List, like String and the rest of java.lang and java.util, lives in the **java.base** module. Mine is compiled onto the class path, so it belongs to the unnamed module, whose name is null.
public is no longer enough
Across modules, only exported packages are accessible. A public class in a package the module doesn't export is invisible to other modules. That's strong encapsulation.
module com.shop.orders {
exports com.shop.orders.api;
}
// com.shop.orders.internal.Helper is
// public, yet other modules can't use itHibernate can't get in
Hibernate fails with InaccessibleObjectException when setting private fields of your entities. module-info already says exports com.shop.entity;. What's missing?
Think about it, then reveal the answer
**opens com.shop.entity;** (or opens ... to the framework). exports only grants access to public members; frameworks that read and write private fields need the package opened for deep reflection.
Rules of the graph
Module dependencies must form a graph without cycles: if a requires b and b requires a, it's a compile error. The usual fix is a third module holding the shared types. And no split packages: a package may belong to only one module.
Why the JDK did it
Modules let the JDK hide internals like jdk.internal.* that libraries used to poke at, and let tools build slim runtimes with only the modules an app needs. For your code: dependencies and API boundaries become explicit, checked by both compiler and JVM.
Key takeaways
- requires: dependency; requires transitive: passed on to consumers
- exports: public types usable at compile and run time
- opens: private members reachable by reflection
- No cycles between modules, no split packages
Run java --list-modules to see how the JDK itself is split into dozens of modules, from java.base to java.sql and jdk.jfr.
Practice questions
What does this print?
class Mine { }
void main() {
var m = String.class.getModule();
System.out.println(m.getName());
var u = Mine.class.getModule();
System.out.println(u.isNamed());
}- java.base false
- java.base true
- null false
- java.lang false
Check your answer
java.base false. String lives in the java.base module. Mine is compiled from a single file onto the class path, so it belongs to the unnamed module.
Hibernate fails with InaccessibleObjectException when setting private fields of your entities. module-info already has `exports com.shop.entity;`. What's missing?
- opens com.shop.entity; (or opens ... to the framework)
- exports com.shop.entity to java.base;
- A public no-arg constructor
- requires hibernate.core;
Check your answer
opens com.shop.entity; (or opens ... to the framework). exports only grants access to public members. Frameworks that read and write private fields need the package opened for deep reflection.