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.