Lesson 39 • Advanced
Modular Java (JPMS)
Before Java 9, any public class was accessible from anywhere — leading to "JAR hell" and accidental dependencies on internal APIs. The Java Platform Module System (JPMS) fixes this with strong encapsulation and explicit dependencies, letting you build large applications that are maintainable and secure.
Before You Start
You should know Maven & Gradle (modules are packaged by build tools) and Interfaces (ServiceLoader uses interface-based design). Understanding Reflection helps for the opens directive.
What You'll Learn
- ✅ Why modules were introduced in Java 9
- ✅ module-info.java: requires, exports, provides
- ✅ Strong encapsulation vs classpath
- ✅ ServiceLoader and the provides/uses pattern
- ✅ Creating custom runtime images with jlink
- ✅ Migration strategies for existing projects
1️⃣ Module Directives
Analogy: Think of a module as an apartment building. exports is the lobby — open to visitors. opens is giving someone a master key (reflection access). requires is your utility connections — you declare which services you need.
| Directive | What It Does | Example |
|---|---|---|
| requires | Declare dependency on module | requires java.sql; |
| exports | Make package visible | exports com.myapp.api; |
| opens | Allow reflection access | opens pkg to gson; |
| provides...with | Register implementation | provides Svc with Impl; |
| uses | Consume a service | uses com.myapp.Plugin; |
Try It: Module Encapsulation
// 💡 Try modifying this code and see what happens!
// Module encapsulation — controlling what's visible
console.log("=== Module Encapsulation ===\n");
// Simulate module system
class Module {
constructor(name) {
this.name = name;
this.packages = {};
this.requires = [];
this.exports = [];
this.opens = [];
}
addPackage(name, classes) { this.packages[name] = classes; }
addRequires(mod) { this.requires.push(mod); }
addExports(pkg) { this.exports.push(pkg); }
add
...2️⃣ ServiceLoader — Plugin System
Analogy: ServiceLoader is like a power outlet standard. The outlet (interface) defines the shape. Different plugs (implementations) from different manufacturers (modules) all fit the same outlet. Your app discovers and loads all available "plugs" at runtime — zero coupling.
Try It: ServiceLoader Plugin System
// 💡 Try modifying this code and see what happens!
// ServiceLoader — dynamic plugin discovery
console.log("=== ServiceLoader Plugin System ===\n");
// 1. Define the service interface (SPI)
console.log("1. DEFINE SERVICE INTERFACE:");
console.log(" // In module: com.myapp.spi");
console.log(" public interface PaymentProcessor {");
console.log(" String name();");
console.log(" boolean process(double amount);");
console.log(" }\n");
// Simulated service interface
class PaymentProc
...Common Mistakes
- ⚠️ Split packages: Two modules can't export the same package — refactor before modularizing
- ⚠️ Reflection blocked: Frameworks like Hibernate need
opensfor entity packages - ⚠️ --add-opens everywhere: If you're adding many flags, your modularization is incomplete
- ⚠️ Trying to modularize everything at once: Migrate incrementally — start with automatic modules
Pro Tips
- 💡 jlink creates a custom JRE with only your modules — 30MB instead of 300MB, perfect for Docker
- 💡 jdeps analyzes JARs and tells you which modules they depend on — invaluable for migration
- 💡 Most libraries work as automatic modules — just put them on the module-path
- 💡 ServiceLoader is the standard plugin mechanism — define interface, let modules provide implementations
Try It: jlink Custom Runtime
// 💡 Try modifying this code and see what happens!
// jlink — create minimal custom Java runtime
console.log("=== jlink Custom Runtime ===\n");
// 1. The problem with full JRE
console.log("1. THE PROBLEM:");
let fullJRE = { modules: 70, size: 300, startupMs: 200 };
console.log(" Full JRE: " + fullJRE.modules + " modules, " + fullJRE.size + "MB, " + fullJRE.startupMs + "ms startup");
console.log(" Your app uses: 5 modules");
console.log(" → Shipping 65 unused modules in your Docker image!\n
...📋 Quick Reference
| Directive | Syntax | Purpose |
|---|---|---|
| requires | requires java.sql; | Depend on module |
| exports | exports com.app.api; | Public API package |
| opens | opens pkg to framework; | Reflection access |
| jlink | jlink --add-modules | Custom slim JRE |
| jdeps | jdeps --module-path | Analyze dependencies |
🎉 Lesson Complete!
You understand Java's module system — the key to building large, maintainable applications! Next: Deployment — packaging, Docker, and deploying Java apps to production.
Sign up for free to track which lessons you've completed and get learning reminders.