Module System Overview
JPMS fundamentally changed how Java applications are structured and deployed:Strong Encapsulation
Packages are private by default, only exported packages are accessible
Explicit Dependencies
Modules declare what they require and provide
Reliable Configuration
Missing dependencies detected at startup
Improved Security
Reduced attack surface through encapsulation
Module Descriptor
Every module contains amodule-info.class file compiled from module-info.java. The Java-level API is defined in src/java.base/share/classes/java/lang/module/ModuleDescriptor.java.
Module Types
FromModuleDescriptor.java:
“A module descriptor describes a normal, open, or automatic module. Normal modules and open modules describe their dependences, exported-packages, the services that they use or provide, and other components. Normal modules may open specific packages. The module descriptor for an open module does not declare any open packages but when instantiated in the Java virtual machine then it is treated as if all packages are open.”
- Normal Module
- Open Module
- Automatic Module
Standard explicit module:
- Explicit dependencies via
requires - Controlled API via
exports - Reflection access via
opens - Service loading via
uses/provides
Module Modifiers
FromModuleDescriptor.java:
VM Implementation
The HotSpot VM implements JPMS through several interconnected subsystems.ModuleEntry
The core VM representation of a module isModuleEntry, defined in src/hotspot/share/classfile/moduleEntry.hpp:
Each module in the VM has an associated
ModuleEntry that tracks its metadata, dependencies, and relationship to other modules.Key Fields
Module Identity:_name- Module name symbol (e.g., “java.base”)_version- Optional version string_location- Module location (JAR path, etc.)_module_handle- Reference tojava.lang.Moduleobject
_reads- Modules this module can access_can_read_all_unnamed- Special flag for automatic modules_loader_data- Associated class loader
_is_open- Whether all packages are unqualifiedly exported_is_patched- Module modified by--patch-module_must_walk_reads- GC safepoint requirement
Module Constants
FrommoduleEntry.hpp:
Module Resolution
Module resolution happens at VM startup through theResolver class (src/java.base/share/classes/java/lang/module/Resolver.java).
Resolution Process
- 1. Discovery
- 2. Resolution
- 3. Configuration
- 4. Instantiation
Find available modules:
- Scan module path
- Identify module descriptors
- Validate module-info.class files
- Build module graph
ModuleFinder interface.Module Layers
Modules are organized into layers:- Has a parent layer (except boot layer)
- Contains a set of modules
- Maps modules to class loaders
- Can be created dynamically
Access Control
JPMS enforces access control at multiple levels:Package Access
Reflection Access
Different levels of reflection access:Normal Module
Public classes/members accessible via reflection only if exported
Opens Directive
opens allows deep reflection to specific modules or all modulesOpen Module
All packages open for deep reflection
Command Line
--add-opens breaks encapsulation at runtimeVM Access Checks
The VM enforces access at:- Class loading time - Verify package exports
- Link resolution time - Check method/field access
- Reflection time - Validate setAccessible() calls
- JNI calls - FindClass respects module boundaries
Service Loading
JPMS integrates withServiceLoader mechanism:
Provider Module
Consumer Module
Service Resolution
At runtime:ServiceLoader.load()called- VM searches all modules with
providesdeclarations - Matching providers instantiated
- Services returned to caller
Service loading respects module boundaries - only providers in readable modules are discovered.
Module Path vs Class Path
Two mechanisms for loading code:| Aspect | Module Path | Class Path |
|---|---|---|
| Units | Modules | JARs/directories |
| Encapsulation | Strong | None |
| Dependencies | Explicit | Implicit |
| Versioning | Module version | None |
| Resolution | Startup | Lazy |
| Split Packages | Forbidden | Allowed |
Unnamed Module
Code on classpath runs in the “unnamed module”:- No name or descriptor
- Exports all packages
- Reads all other modules
- Cannot be required by named modules
Platform Modules
The JDK is modularized into ~70 modules:Core Modules
JDK Modules
Module Graph
All modules transitively requirejava.base:
Implementation Details
JVM_DefineModule
Native method that defines a module in the VM:Package Exports
Packages tracked viaPackageEntry objects:
Module Reads
Readability established throughModuleEntry::_reads:
Command Line Options
JPMS behavior can be modified:Breaking Encapsulation
Module Dependencies
Patching Modules
Migration Strategies
Bottom-Up Migration
- Modularize low-level libraries first
- Work up dependency tree
- Application modules last
Top-Down Migration
- Create automatic modules from JARs
- Gradually add module descriptors
- Refine dependencies over time
Hybrid Approach
- Use automatic modules for dependencies
- Modularize your code
- Wait for library updates
Automatic modules are the bridge between classpath and module path, allowing incremental migration.
Performance Considerations
Faster Class Loading
- Module boundaries enable better class lookup
- No need to scan entire classpath
- Package ownership clearly defined
Smaller Runtime Images
jlink creates custom runtime images:
Improved Security
- Reduced attack surface (unexported packages)
- Stronger encapsulation than classpath
- Critical internal APIs protected
Debugging Modules
Module Information
Dependency Analysis
Next Steps
Architecture Overview
Return to architecture overview
HotSpot VM
Explore VM internals