A common misconception about the Java Platform Module System (JPMS) is that modules are static—you define module-info.java
, compile the code, and that’s it. But what happens when you want to dynamically load new functionality at runtime?
Developers migrating from classpath-based systems often struggle here. On the classpath, you could drop new JARs in a folder and load them reflectively. With JPMS, however, strong encapsulation and resolution rules prevent ad-hoc loading. This is where the Layer API comes in—it allows applications to create new module layers at runtime and dynamically integrate new modules.
This is particularly useful in large enterprise systems, plugin architectures, and microservices, where modules may need to be added or replaced without restarting the entire application.
What is the Layer API?
The Layer API enables dynamic loading of modules into separate runtime layers, providing isolation and control.
Key Concepts:
- Boot Layer → Contains all system and application modules loaded at startup.
- Child Layers → New layers created dynamically, loading additional modules.
- Configuration → Defines how modules are resolved in a layer.
- ModuleFinder → Helps discover available modules.
Example: Creating a Dynamic Module Layer
Step 1: Prepare Modules
// module-info.java for plugin module
module com.example.plugin {
exports com.example.plugin.api;
}
Step 2: Load Module with Layer API
ModuleFinder finder = ModuleFinder.of(Path.of("plugins"));
Configuration parentConfig = ModuleLayer.boot().configuration();
Configuration pluginConfig = parentConfig.resolve(finder, ModuleFinder.of(), Set.of("com.example.plugin"));
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
ModuleLayer pluginLayer = ModuleLayer.defineModulesWithOneLoader(pluginConfig, List.of(ModuleLayer.boot()), systemClassLoader);
// Access module from new layer
Module pluginModule = pluginLayer.findModule("com.example.plugin").orElseThrow();
System.out.println("Loaded module: " + pluginModule.getName());
This loads the com.example.plugin
module dynamically into a new layer.
Example: Using Services in Dynamic Modules
Dynamic modules work seamlessly with the ServiceLoader API.
ServiceLoader<com.example.plugin.api.Plugin> loader =
ServiceLoader.load(pluginLayer, com.example.plugin.api.Plugin.class);
for (com.example.plugin.api.Plugin plugin : loader) {
plugin.execute();
}
This allows applications to dynamically load new service providers at runtime.
Pitfalls & Misuse Cases
- Classpath Assumptions → Dynamic classpath tricks don’t work in JPMS.
- Layer Explosion → Creating too many layers increases complexity.
- Split Packages → Still not allowed in dynamic layers.
- Security Concerns → Opening modules dynamically can reintroduce vulnerabilities.
Best Practices for Dynamic Modules
- ✅ Use layers for plugin systems and dynamic loading.
- ✅ Keep dynamic modules self-contained with clear exports.
- ✅ Use
ServiceLoader
for extension points. - ✅ Avoid overusing dynamic layers—most apps should rely on static boot layers.
- ✅ Document why a dynamic module is needed.
- ✅ Use targeted
opens
if reflection is required.
📌 What's New in Java Versions?
- Java 5 → N/A (modules introduced in Java 9)
- Java 9 → JPMS introduced Layer API for dynamic modules
- Java 11 → Refinements in ServiceLoader integration with modules
- Java 17 → Performance improvements in module resolution
- Java 21 → No significant updates across Java versions for this feature
Analogy
Think of the Layer API like an event hall with multiple stages:
- The boot layer is the main stage where the core performance happens.
- Child layers are smaller side stages where new acts (modules) can be added without interrupting the main show.
- The audience (application) can attend both, but each stage controls who gets access to its performers (APIs).
Summary + Key Takeaways
- The Layer API enables dynamic loading of modules at runtime.
- It allows building plugin systems and extensible architectures.
- Always prefer
ServiceLoader
for extension points. - Avoid pitfalls like split packages and overuse of open modules.
- Use dynamic modules strategically, not as a replacement for static modularization.
FAQs
Q1. What is the difference between the classpath and module path?
Classpath exposes everything globally; module path enforces explicit boundaries.
Q2. Why do I get “module not found” errors when using Layer API?
Because the module isn’t present in the ModuleFinder
path or not declared correctly.
Q3. Can dynamic modules access boot layer modules?
Yes, if they declare proper requires
dependencies.
Q4. Can boot layer modules access dynamic modules?
No, unless explicitly loaded via ServiceLoader.
Q5. What is the purpose of requires transitive
in dynamic modules?
It ensures downstream modules automatically inherit dependencies.
Q6. Can I use opens
with dynamically loaded modules?
Yes, but it should be targeted to specific frameworks.
Q7. What are automatic modules in this context?
Non-modular JARs that can be loaded as temporary modules in layers.
Q8. How does JPMS improve security compared to classpath?
By hiding internals, preventing split packages, and controlling reflection.
Q9. When should I use jlink
vs jmod
with dynamic modules?
jlink
→ Build optimized runtime images.jmod
→ Package modules for distribution.
Q10. Do frameworks like Spring or Hibernate support Layer API?
They generally work but may require opens
for reflection.