1. The Hook (The "Byte-Sized" Intro)
- In a Nutshell: Java Platform Module System (JPMS) (Java 9+, Project Jigsaw) introduces modules—strong encapsulation units. module-info.java declares dependencies. requires (dependencies), exports (public API).
- Benefits: Strong encapsulation (hide internals), reliable configuration (explicit dependencies), improved security, smaller runtime (jlink custom JRE). Modular JDK (java.base, java.sql, etc.).
- Migration: classpath → module path.
- Key: Modules > packages in hierarchy!
Think of apartment building. Before modules = open floor (no walls, everything accessible, messy). After modules = separate apartments with locked doors (exports = public entrance, requires = utilities needed). Can't access neighbor's bedroom (strong encapsulation). Building manager knows which apartments need electricity (reliable configuration)!
2. Conceptual Clarity (The "Simple" Tier)
💡 The Analogy
- Module: Apartment with controlled access
- exports: Front door (public API)
- requires: Utilities needed (dependencies)
- module-info.java: Apartment contract/lease
Module Hierarchy
graph TD
A[JDK] --> B[java.base module]
A --> C[java.sql module]
A --> D[java.xml module]
E[Your Application] --> F[com.myapp module]
F -->|requires| B
F -->|requires| C
F --> G[exports com.myapp.api]
F --> H[internal packages<br/>not exported]
style B fill:#2E7D32
style F fill:#F57C00
style G fill:#2E7D32
style H fill:#C2185B
3. Technical Mastery (The "Deep Dive")
Module System Benefits
| Benefit | Description |
|---|---|
| Strong Encapsulation | Hide internal APIs, only export what's needed |
| Reliable Configuration | Explicit dependencies, fail-fast at startup |
| Improved Security | Smaller attack surface |
| Smaller Runtime | jlink creates custom JRE with only needed modules |
| Better Performance | JVM optimizations with module metadata |
4. Interactive & Applied Code
java
// ========================================
// MODULE 1: com.myapp.core
// File: src/com.myapp.core/module-info.java
// ========================================
module com.myapp.core {
// Dependencies
requires java.base; // Implicit, always available
requires java.sql; // Need JDBC
// Public API (exported)
exports com.myapp.core.api;
exports com.myapp.core.model;
// Internal packages (NOT exported)
// com.myapp.core.internal - hidden from outside
}
// File: src/com.myapp.core/com/myapp/core/api/Service.java
package com.myapp.core.api;
public class Service {
public void doSomething() {
System.out.println("Public API method");
InternalHelper.help(); // OK: same module
}
}
// File: src/com.myapp.core/com/myapp/core/internal/InternalHelper.java
package com.myapp.core.internal;
public class InternalHelper {
public static void help() {
System.out.println("Internal helper (not exported)");
}
}
// ========================================
// MODULE 2: com.myapp.client
// File: src/com.myapp.client/module-info.java
// ========================================
module com.myapp.client {
requires com.myapp.core; // Depends on core module
requires java.base;
}
// File: src/com.myapp.client/com/myapp/client/Main.java
package com.myapp.client;
import com.myapp.core.api.Service; // ✅ OK: exported
// import com.myapp.core.internal.InternalHelper; // ❌ ERROR: not exported!
public class Main {
public static void main(String[] args) {
Service service = new Service();
service.doSomething();
// InternalHelper.help(); // ❌ COMPILE ERROR: package not visible!
}
}
// ========================================
// EXAMPLE: Transitive Dependencies
// ========================================
// module-info.java for library module
module com.myapp.library {
requires transitive java.sql; // Transitive: clients get java.sql too
exports com.myapp.library;
}
// module-info.java for client
module com.myapp.client {
requires com.myapp.library; // Gets java.sql transitively!
// No need to: requires java.sql;
}
// ========================================
// EXAMPLE: Opens (Reflection Access)
// ========================================
module com.myapp.reflection {
// Normal export (compile-time access only)
exports com.myapp.api;
// Opens for reflection (runtime access)
opens com.myapp.internal.dto; // Frameworks can reflect
// Or open entire module
// open module com.myapp.reflection { ... }
}
// ========================================
// EXAMPLE: Services (Dependency Injection)
// ========================================
// Service interface
package com.myapp.service;
public interface Logger {
void log(String message);
}
// module-info.java (service provider)
module com.myapp.logger.impl {
requires com.myapp.service;
provides com.myapp.service.Logger
with com.myapp.logger.impl.ConsoleLogger;
}
// module-info.java (service consumer)
module com.myapp.app {
requires com.myapp.service;
uses com.myapp.service.Logger; // Use service
}
// Client code
import java.util.ServiceLoader;
import com.myapp.service.Logger;
ServiceLoader<Logger> loader = ServiceLoader.load(Logger.class);
Logger logger = loader.findFirst().orElseThrow();
logger.log("Hello from module system!");Compiling and Running Modules:
bash
# Directory structure:
# src/
# com.myapp.core/
# module-info.java
# com/myapp/core/api/Service.java
# com.myapp.client/
# module-info.java
# com/myapp/client/Main.java
# Compile modules
javac -d mods/com.myapp.core \
src/com.myapp.core/module-info.java \
src/com.myapp.core/com/myapp/core/api/*.java
javac --module-path mods -d mods/com.myapp.client \
src/com.myapp.client/module-info.java \
src/com.myapp.client/com/myapp/client/*.java
# Run modular application
java --module-path mods -m com.myapp.client/com.myapp.client.Main
# Create custom runtime with jlink
jlink --module-path mods:$JAVA_HOME/jmods \
--add-modules com.myapp.client \
--output custom-jre
# Run with custom JRE
./custom-jre/bin/java -m com.myapp.client/com.myapp.client.Main5. The Comparison & Decision Layer
| Before Modules (Classpath) | After Modules (Module Path) |
|---|---|
| All public classes accessible | Only exported packages accessible |
| JAR hell (version conflicts) | Reliable configuration |
| No encapsulation | Strong encapsulation |
| Monolithic JRE | Custom runtime (jlink) |
| Runtime errors | Compile-time errors |
6. The "Interview Corner" (The Edge)
The "Killer" Interview Question: "What's the difference between exports and opens?" Answer:
- exports: Compile-time + runtime access (normal usage)
- opens: Runtime reflection access only (for frameworks)
java
module com.myapp {
exports com.myapp.api; // ✅ Public API
opens com.myapp.internal.dto; // ✅ For reflection (JSON libs, JPA)
}
// Client code:
import com.myapp.api.Service; // ✅ OK: exported
// import com.myapp.internal.dto.User; // ❌ ERROR: not exported
// But reflection works:
Class<?> cls = Class.forName("com.myapp.internal.dto.User"); // ✅ OK: openedPro-Tips:
- Unnamed module (classpath):
bash
# Mix modules and classpath
java --module-path mods --class-path legacy.jar \
-m com.myapp/com.myapp.Main
# Classpath = unnamed module (can access all modules)- Automatic modules (JAR without module-info):
bash
# Put non-modular JAR on module path
java --module-path legacy.jar:mods -m com.myapp/Main
# JAR name becomes module name: legacy.jar → legacy- Migration strategy:
- Bottom-up: Start with leaf dependencies, make them modules
- Top-down: Application first, dependencies as automatic modules