Stage 4: Modern Java, lesson 6 of 7

Modules (JPMS)

Advanced3 min read@since 9Code runs on your Java 25
Explain it forThe essentials plus production detail and pitfalls.

The Java Platform Module System (Java 9) groups packages into modules with explicit boundaries. A module-info.java file declares:

  • requires: modules this one depends on.
  • exports: packages other modules may use. Everything else is hidden, even public classes.
  • opens: packages open to deep reflection, which frameworks like Hibernate and Jackson need.
  • provides … with and uses: service loading.

The JDK itself is modular (java.base, java.sql, java.net.http…), which lets jlink build a small custom runtime containing only the modules your app needs.

Most Spring Boot applications still run on the classpath without a module-info.java. Modules shine in libraries and in apps that want strong encapsulation or tiny runtimes. Java 25's import module java.base; is a separate, simpler feature that imports every package a module exports.

Example

Java
// src/main/java/module-info.java
module com.javaatlas.billing {
    requires java.net.http;             // uses the HTTP client
    requires transitive java.sql;       // our public API exposes java.sql types

    exports com.javaatlas.billing.api;                                        // the public API
    opens com.javaatlas.billing.model to com.fasterxml.jackson.databind;      // reflection for JSON
}
Build a small runtime
# See which modules a JAR needs, then build a runtime with only those
jdeps --print-module-deps target/app.jar
jlink --add-modules java.base,java.net.http,java.sql --output custom-jre --strip-debug --no-header-files
./custom-jre/bin/java -jar target/app.jar

Common mistake

Adding module-info.java to a Spring Boot app without opening packages for reflection. The frameworks then fail with InaccessibleObjectException.

Under the hood

Strong encapsulation of JDK internals (the default since Java 16, with no opt-out since 17) is why old libraries that used sun.misc or private JDK fields broke on upgrade; the fix is a newer library version or, temporarily, --add-opens. Split packages (the same package in two modules) are forbidden and often block migrations. A plain JAR on the module path becomes an automatic module named after the JAR.

Check yourself

Which tool builds a smaller custom Java runtime?

How this connects

Where this leads

You've reached the end of this thread. Try a learning path for what's next.

Part of Upgrade from Java 8 to Java 25.

Was this lesson helpful?

Finished reading? Mark it complete to track your progress.