🪞 Reflection, Annotations & Modules · Advanced

The module system (JPMS) in Java

module-info.java, requires, exports, opens, strong encapsulation.

🧩 The mysteryYour class is 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.

🔮 Predict it

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());
}
  1. java.base null
  2. java.util null
  3. java.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.

⚠️ The trap

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 it
🤔 Think first

Hibernate 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.

💼 In the real world

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

  1. requires: dependency; requires transitive: passed on to consumers
  2. exports: public types usable at compile and run time
  3. opens: private members reachable by reflection
  4. No cycles between modules, no split packages
🤯 Did you know?

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());
}
  1. java.base false
  2. java.base true
  3. null false
  4. 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?

  1. opens com.shop.entity; (or opens ... to the framework)
  2. exports com.shop.entity to java.base;
  3. A public no-arg constructor
  4. 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.

Most real apps still put JARs on the class path. Next: how old JARs and new modules live together.