25.2 Java Platform Module System (JPMS) Security
JPMS provides strong encapsulation and security through module boundaries, replacing some functionality of the deprecated Security Manager.
Module Encapsulation Fundamentals
// Module Security Concepts
public class JPMSSecurityConcepts {
public static void printJPMSBenefits() {
System.out.println("=== JPMS SECURITY BENEFITS ===");
System.out.println("\n--- STRONG ENCAPSULATION ---");
System.out.println("✓ Internal packages are hidden by default");
System.out.println("✓ Only exported packages are accessible");
System.out.println("✓ Reflection limited to exported packages");
System.out.println("✓ Compile-time and runtime enforcement");
System.out.println("\n--- RELIABLE CONFIGURATION ---");
System.out.println("✓ Explicit dependencies declared");
System.out.println("✓ No split packages");
System.out.println("✓ No circular dependencies at module level");
System.out.println("✓ Missing dependencies detected at startup");
System.out.println("\n--- SECURITY IMPROVEMENTS ---");
System.out.println("✓ Reduces attack surface");
System.out.println("✓ Prevents access to internal APIs");
System.out.println("✓ Better than classpath isolation");
System.out.println("✓ Replacement for SecurityManager encapsulation");
}
}
Module Declaration Examples
// Basic module declaration
// File: module-info.java
// 1. Simple module with exports
module com.example.api {
// Export public API packages
exports com.example.api;
exports com.example.api.model;
// Internal packages (not exported) are hidden:
// - com.example.internal
// - com.example.impl
}
// 2. Module with dependencies
module com.example.service {
// Require other modules
requires java.base; // implicit, always available
requires java.sql; // explicit dependency
requires com.example.api; // custom module
// Export service packages
exports com.example.service;
}
// 3. Qualified exports (restrict to specific modules)
module com.example.internal {
// Only com.example.service can access this package
exports com.example.internal.spi to com.example.service;
// Public API for everyone
exports com.example.internal.api;
}
// 4. Module with services
module com.example.plugin {
// Provide service implementation
provides com.example.api.PluginService
with com.example.plugin.DefaultPlugin;
// Use services from other modules
uses com.example.api.DataProvider;
}
// 5. Open module (for reflection)
open module com.example.framework {
// All packages open for reflection
exports com.example.framework;
}
// 6. Selective opens (for specific packages)
module com.example.data {
exports com.example.data.api;
// Allow reflection on entities (for JPA, Jackson, etc.)
opens com.example.data.entity to
org.hibernate.orm.core,
com.fasterxml.jackson.databind;
}
// 7. Transitive dependencies
module com.example.full {
// Modules requiring this module also get java.sql
requires transitive java.sql;
exports com.example.full;
}
Module Encapsulation in Practice
// Module structure example
// Project structure:
// src/
// com.example.app/
// module-info.java
// com/example/app/
// Main.java
// api/
// Service.java (public API)
// internal/
// ServiceImpl.java (hidden implementation)
// module-info.java
module com.example.app {
exports com.example.app.api;
// com.example.app.internal is NOT exported
}
// Service.java (public API)
package com.example.app.api;
public interface Service {
String execute(String input);
}
// ServiceImpl.java (hidden implementation)
package com.example.app.internal;
import com.example.app.api.Service;
// This class is NOT accessible outside the module
public class ServiceImpl implements Service {
@Override
public String execute(String input) {
return processInternal(input);
}
// Private implementation details
private String processInternal(String input) {
return "Processed: " + input;
}
}
// Factory in public API
package com.example.app.api;
public class ServiceFactory {
public static Service createService() {
// Can access internal package within same module
return new com.example.app.internal.ServiceImpl();
}
}
Reflection Restrictions
// Reflection with JPMS
public class JPMSReflection {
// Attempting to access non-exported package
public static void demonstrateReflectionRestrictions() {
System.out.println("=== JPMS REFLECTION RESTRICTIONS ===");
try {
// This works: exported package
Class<?> publicClass = Class.forName("com.example.app.api.Service");
System.out.println("✓ Can load exported class: " +
publicClass.getName());
} catch (ClassNotFoundException e) {
System.out.println("✗ Cannot load class: " + e.getMessage());
}
try {
// This fails: non-exported package (unless opened)
Class<?> internalClass = Class.forName(
"com.example.app.internal.ServiceImpl");
System.out.println("✓ Can load internal class: " +
internalClass.getName());
} catch (IllegalAccessException | ClassNotFoundException e) {
System.out.println("✗ Cannot access internal class: " +
e.getMessage());
}
}
// Opening packages for reflection
public static void demonstrateOpens() {
System.out.println("\n=== OPENS DIRECTIVE ===");
System.out.println("Without 'opens':");
System.out.println(" - Deep reflection blocked");
System.out.println(" - setAccessible() fails");
System.out.println(" - Private fields inaccessible");
System.out.println("\nWith 'opens com.example.entity':");
System.out.println(" - Deep reflection allowed");
System.out.println(" - setAccessible() succeeds");
System.out.println(" - Private fields accessible");
System.out.println("\nWith 'opens com.example.entity to hibernate.core':");
System.out.println(" - Only hibernate.core can reflect");
System.out.println(" - Other modules blocked");
}
}
// Example: JPA entity with opens
// module-info.java
module com.example.jpa {
requires java.persistence;
// Export API
exports com.example.jpa.repository;
// Open entities for JPA provider reflection
opens com.example.jpa.entity to org.hibernate.orm.core;
}
// Entity class
package com.example.jpa.entity;
import javax.persistence.*;
@Entity
public class User {
@Id
@GeneratedValue
private Long id;
private String username;
private String email;
// Getters and setters
}
Module Path vs Classpath
// Module path security comparison
public class ModulePathSecurity {
public static void printComparison() {
System.out.println("=== MODULE PATH vs CLASSPATH ===");
System.out.println("\n--- CLASSPATH (Legacy) ---");
System.out.println("✗ All public classes accessible");
System.out.println("✗ Internal APIs exposed");
System.out.println("✗ No encapsulation boundary");
System.out.println("✗ Split packages allowed");
System.out.println("✗ Dependency hell");
System.out.println("\n--- MODULE PATH (Modern) ---");
System.out.println("✓ Only exported packages accessible");
System.out.println("✓ Internal APIs hidden");
System.out.println("✓ Strong encapsulation");
System.out.println("✓ No split packages");
System.out.println("✓ Reliable configuration");
System.out.println("\n--- MIGRATION ---");
System.out.println("Automatic modules (classpath JARs on module path):");
System.out.println(" - Exports all packages");
System.out.println(" - Reads all other modules");
System.out.println(" - Bridge between classpath and module path");
System.out.println("\nNamed modules (proper module-info.java):");
System.out.println(" - Explicit exports");
System.out.println(" - Explicit requires");
System.out.println(" - Strong encapsulation");
}
}
Breaking Encapsulation (When Necessary)
// Breaking JPMS encapsulation for compatibility
public class JPMSCompatibility {
public static void printCompatibilityOptions() {
System.out.println("=== BREAKING ENCAPSULATION (USE SPARINGLY) ===");
System.out.println("\n--- COMMAND-LINE OPTIONS ---");
System.out.println("\n1. --add-opens");
System.out.println(" Opens package for deep reflection");
System.out.println(" Example:");
System.out.println(" java --add-opens java.base/java.lang=ALL-UNNAMED \\");
System.out.println(" --add-opens java.base/java.util=mymodule \\");
System.out.println(" -jar app.jar");
System.out.println("\n2. --add-exports");
System.out.println(" Exports package to specific module");
System.out.println(" Example:");
System.out.println(" java --add-exports java.base/sun.nio.ch=ALL-UNNAMED \\");
System.out.println(" -jar app.jar");
System.out.println("\n3. --add-reads");
System.out.println(" Adds module dependency");
System.out.println(" Example:");
System.out.println(" java --add-reads mymodule=othermodule \\");
System.out.println(" -jar app.jar");
System.out.println("\n4. --illegal-access=permit");
System.out.println(" (Deprecated) Allow illegal access with warnings");
System.out.println(" Removed in Java 17+");
System.out.println("\n--- USE CASES ---");
System.out.println("✓ Legacy frameworks requiring deep reflection");
System.out.println("✓ Dependency injection frameworks (Spring, CDI)");
System.out.println("✓ ORM frameworks (Hibernate, JPA)");
System.out.println("✓ Serialization libraries (Jackson, Gson)");
System.out.println("✓ Testing frameworks (JUnit, Mockito)");
System.out.println("\n--- BEST PRACTICES ---");
System.out.println("1. Avoid if possible");
System.out.println("2. Document why it's necessary");
System.out.println("3. Limit scope (specific modules, not ALL-UNNAMED)");
System.out.println("4. Create feature request with library maintainer");
System.out.println("5. Plan to remove when library adds proper support");
}
}
Service Provider Interface (SPI) with Modules
// Service-based architecture with JPMS
// Service interface module
module com.example.api {
exports com.example.api;
}
package com.example.api;
public interface DataProcessor {
String process(String data);
}
// Service provider module 1
module com.example.provider.xml {
requires com.example.api;
provides com.example.api.DataProcessor
with com.example.provider.xml.XmlProcessor;
}
package com.example.provider.xml;
import com.example.api.DataProcessor;
public class XmlProcessor implements DataProcessor {
@Override
public String process(String data) {
return "<xml>" + data + "</xml>";
}
}
// Service provider module 2
module com.example.provider.json {
requires com.example.api;
provides com.example.api.DataProcessor
with com.example.provider.json.JsonProcessor;
}
package com.example.provider.json;
import com.example.api.DataProcessor;
public class JsonProcessor implements DataProcessor {
@Override
public String process(String data) {
return "{\"data\": \"" + data + "\"}";
}
}
// Consumer module
module com.example.consumer {
requires com.example.api;
uses com.example.api.DataProcessor;
}
package com.example.consumer;
import com.example.api.DataProcessor;
import java.util.ServiceLoader;
public class Application {
public static void main(String[] args) {
ServiceLoader<DataProcessor> loader =
ServiceLoader.load(DataProcessor.class);
System.out.println("=== AVAILABLE PROCESSORS ===");
for (DataProcessor processor : loader) {
System.out.println("Processor: " +
processor.getClass().getName());
System.out.println("Result: " +
processor.process("test"));
}
}
}
Module Dependency Analysis
// Analyzing module dependencies
public class ModuleDependencyAnalysis {
public static void analyzeCurrentModule() {
Module module = ModuleDependencyAnalysis.class.getModule();
System.out.println("=== CURRENT MODULE ANALYSIS ===");
System.out.println("Module name: " + module.getName());
System.out.println("Is named: " + module.isNamed());
System.out.println("\nExported packages:");
module.getDescriptor().exports().forEach(exports -> {
System.out.println(" - " + exports.source());
if (!exports.targets().isEmpty()) {
System.out.println(" to: " + exports.targets());
}
});
System.out.println("\nRequired modules:");
module.getDescriptor().requires().forEach(requires -> {
System.out.println(" - " + requires.name());
if (requires.modifiers().contains(
java.lang.module.ModuleDescriptor.Requires.Modifier.TRANSITIVE)) {
System.out.println(" (transitive)");
}
});
System.out.println("\nProvided services:");
module.getDescriptor().provides().forEach(provides -> {
System.out.println(" - " + provides.service());
System.out.println(" implementations: " + provides.providers());
});
}
// Check if package is exported
public static boolean isPackageExported(String packageName) {
Module module = ModuleDependencyAnalysis.class.getModule();
return module.isExported(packageName);
}
// Check if can read another module
public static boolean canReadModule(String moduleName) {
Module module = ModuleDependencyAnalysis.class.getModule();
ModuleLayer layer = module.getLayer();
if (layer == null) return false;
return layer.findModule(moduleName)
.map(module::canRead)
.orElse(false);
}
}
Migration Strategy
// JPMS Migration Guide
public class JPMSMigration {
public static void printMigrationStrategy() {
System.out.println("=== JPMS MIGRATION STRATEGY ===");
System.out.println("\n--- PHASE 1: ANALYSIS ---");
System.out.println("1. Run jdeps to analyze dependencies");
System.out.println(" jdeps -s app.jar");
System.out.println(" jdeps --generate-module-info . app.jar");
System.out.println("\n2. Identify internal API usage");
System.out.println(" jdeps --jdk-internals app.jar");
System.out.println("\n--- PHASE 2: BOTTOM-UP MIGRATION ---");
System.out.println("1. Start with modules with no dependencies");
System.out.println("2. Add module-info.java");
System.out.println("3. Export public API packages");
System.out.println("4. Test thoroughly");
System.out.println("5. Move up dependency tree");
System.out.println("\n--- PHASE 3: TOP-DOWN REFINEMENT ---");
System.out.println("1. Remove unnecessary exports");
System.out.println("2. Use qualified exports where appropriate");
System.out.println("3. Add opens for reflection-needing packages");
System.out.println("4. Implement services for extensibility");
System.out.println("\n--- COMMON ISSUES ---");
System.out.println("Split packages:");
System.out.println(" - Rename packages in one module");
System.out.println(" - Use package relocation in build tool");
System.out.println("\nReflection failures:");
System.out.println(" - Add 'opens' directive");
System.out.println(" - Use --add-opens as last resort");
System.out.println("\nInternal API usage:");
System.out.println(" - Find replacement public API");
System.out.println(" - Use --add-exports temporarily");
System.out.println(" - File issue with library maintainer");
}
}
Best Practices
- Export only public APIs: Keep internal packages hidden.
- Use qualified exports: Restrict access to specific modules when possible.
- Minimize opens: Only open packages that truly need deep reflection.
- Transitive requires carefully: Use transitive only when consuming modules need it.
- Service-based design: Use SPI for extensibility instead of reflection.
- Avoid --add-opens: Prefer proper module declarations.
- Document breaking encapsulation: If you must break it, document why.
- Module naming: Use reverse DNS (com.example.module).
- Version modules: Consider using module versioning.
- Test module boundaries: Verify encapsulation works as expected.