Comprehensive guide to Java Module System directives for controlling module dependencies and accessibility.
1. Module System Overview
// Basic module structure
module com.example.myapp {
requires java.base; // Dependency on other modules
requires java.sql;
exports com.example.api; // Public API
exports com.example.util to com.example.test;
opens com.example.model; // Reflection access
opens com.example.internal to spring.core;
uses com.example.spi.ServiceProvider;
provides com.example.spi.ServiceProvider
with com.example.impl.DefaultServiceProvider;
}
2. Requires Directive
Basic Requires
// Simple dependencies
module com.example.persistence {
requires java.sql; // Compile-time and runtime dependency
requires java.logging; // JDK logging
requires java.transaction.xa; // XA transactions
}
module com.example.web {
requires com.example.persistence; // Our own module
requires java.net.http; // HTTP client
requires jdk.httpserver; // HTTP server
}
Requires Static
// Optional dependencies - compile-time only
module com.example.optional.features {
requires static java.xml; // Optional XML support
requires static jdk.compiler; // Optional compiler API
requires static com.thirdparty.lib; // Optional 3rd party
}
// Usage with conditional code
public class OptionalFeature {
public void processXml(String xml) {
// Check if XML module is available at runtime
Module xmlModule = ModuleLayer.boot().findModule("java.xml").orElse(null);
if (xmlModule != null) {
// Use XML features
processWithXml(xml);
} else {
// Fallback implementation
processWithoutXml(xml);
}
}
private void processWithXml(String xml) {
try {
// XML processing code that will only be called if module is present
} catch (NoClassDefFoundError e) {
// Handle missing optional dependency
}
}
}
Requires Transitive
// Module that exposes dependencies to its consumers
module com.example.framework {
requires transitive java.sql; // Consumers get java.sql automatically
requires transitive java.logging; // Consumers get logging automatically
requires com.internal.lib; // Internal dependency, not exposed
exports com.example.framework.api;
}
// Consumer module - automatically gets transitive dependencies
module com.example.myapp {
requires com.example.framework; // Implicitly gets java.sql and java.logging
// No need to explicitly require java.sql or java.logging
exports com.example.myapp.ui;
}
Complete Requires Example
// Comprehensive module with different require types
module com.example.enterprise.app {
// Standard dependencies
requires java.base; // Implicit, but explicit for clarity
// Runtime dependencies
requires java.sql;
requires java.naming; // JNDI
requires java.management; // JMX
// Optional features
requires static java.xml.bind; // JAX-B (optional)
requires static java.compiler; // Annotation processing (optional)
// Framework dependencies with transitive exposure
requires transitive com.example.security;
requires transitive com.example.logging;
// Internal implementation dependencies (not exposed)
requires com.example.internal.utils;
requires com.example.internal.cache;
exports com.example.enterprise.app.api;
exports com.example.enterprise.app.spi;
}
3. Exports Directive
Basic Exports
// Module exposing public API
module com.example.library {
requires java.base;
// Public API packages
exports com.example.library.core;
exports com.example.library.api;
exports com.example.library.util;
// Internal packages - not exported
// com.example.library.internal.* is hidden
}
// Public API classes
package com.example.library.core;
/**
* Public class available to all modules
*/
public class LibraryCore {
public void publicMethod() { /* ... */ }
public static final String VERSION = "1.0";
}
package com.example.library.api;
public interface Service {
void execute();
}
// Internal implementation - not exported
package com.example.library.internal;
class InternalImplementation { // Package-private - not accessible outside
void internalMethod() { /* ... */ }
}
Qualified Exports
// Selective exporting to specific modules
module com.example.security {
requires java.base;
requires transitive java.security;
// Public API for all consumers
exports com.example.security.api;
// Internal API only for specific framework modules
exports com.example.security.internal to
com.example.framework,
com.example.integration,
org.springframework.core;
// SPI for service providers
exports com.example.security.spi to
com.example.security.providers;
}
// Framework module that needs internal access
module com.example.framework {
requires com.example.security; // Gets both api and internal (via qualified export)
exports com.example.framework.core;
}
// Regular application - only gets public API
module com.example.myapp {
requires com.example.security; // Only gets com.example.security.api
// CANNOT access com.example.security.internal
}
Export Examples with Different Scenarios
// Multi-layer architecture module
module com.example.layered.app {
requires java.base;
requires java.sql;
// Public facade layer
exports com.example.layered.app.facade;
// Service layer - only for specific business modules
exports com.example.layered.app.service to
com.example.business.module1,
com.example.business.module2;
// DAO layer - only for service implementation
exports com.example.layered.app.dao to
com.example.layered.app.service.impl;
// Internal utilities - not exported at all
// com.example.layered.app.internal.*
}
// API and implementation separation
module com.example.service.api {
exports com.example.service.api;
exports com.example.service.spi;
}
module com.example.service.impl {
requires com.example.service.api;
requires transitive com.example.persistence;
// Implementation details not exported
// Only visible to the API module through services
}
4. Opens Directive
Basic Opens for Reflection
// Module allowing reflection on specific packages
module com.example.persistence.entity {
requires java.persistence;
// Allow full reflection access to entity packages
opens com.example.persistence.entity.model;
opens com.example.persistence.entity.audit;
// Public API for compile-time access
exports com.example.persistence.entity.api;
}
// Entity classes that need reflection access
package com.example.persistence.entity.model;
@Entity
@Table(name = "users")
public class User {
@Id
private Long id;
@Column(name = "username")
private String username;
// Private fields accessed via reflection by JPA providers
private String password;
// Getters and setters will be called via reflection
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
}
Qualified Opens
// Selective reflection access to specific frameworks
module com.example.spring.app {
requires java.base;
requires static spring.core;
requires static spring.context;
requires static hibernate.core;
// Public API
exports com.example.spring.app.service;
exports com.example.spring.app.repository;
// Reflection access for specific frameworks only
opens com.example.spring.app.entity to
spring.core,
spring.beans,
spring.context,
hibernate.core,
org.hibernate.orm;
opens com.example.spring.app.config to
spring.core,
spring.context;
// Internal packages - no reflection access
// com.example.spring.app.internal.*
}
// Configuration class needing reflection
package com.example.spring.app.config;
@Configuration
@ComponentScan
public class AppConfig {
@Bean
public DataSource dataSource() {
// Spring will call this via reflection
return new HikariDataSource();
}
@Bean
@Profile("dev")
public Service devService() {
// Reflection access needed for profile annotation
return new DevService();
}
}
Opens vs Exports Difference
module com.example.demonstration {
exports com.example.demonstration.api; // Compile-time access
opens com.example.demonstration.model; // Runtime reflection access
// Package with both export and open
exports com.example.demonstration.shared;
opens com.example.demonstration.shared; // Both compile-time and reflection
}
// Test demonstrating the difference
public class AccessTest {
public void testExports() {
// This works - exported package
PublicClass obj = new PublicClass();
obj.publicMethod();
// This works - shared package (exported)
SharedClass shared = new SharedClass();
shared.publicMethod();
}
public void testReflection() throws Exception {
// This works - opened package
Class<?> modelClass = Class.forName("com.example.demonstration.model.ModelEntity");
Field privateField = modelClass.getDeclaredField("privateData");
privateField.setAccessible(true); // Allowed because package is opened
// This works - shared package (opened)
Class<?> sharedClass = Class.forName("com.example.demonstration.shared.SharedEntity");
Field sharedField = sharedClass.getDeclaredField("internalState");
sharedField.setAccessible(true); // Allowed because package is opened
// This would fail - api package is only exported, not opened
// Class<?> apiClass = Class.forName("com.example.demonstration.api.ApiClass");
// Field apiField = apiClass.getDeclaredField("internalField");
// apiField.setAccessible(true); // Would throw IllegalAccessException
}
}
5. Real-world Module Examples
Enterprise Application Structure
// Domain module - core business entities
module com.enterprise.domain {
requires java.base;
// Public API
exports com.enterprise.domain.model;
exports com.enterprise.domain.service;
exports com.enterprise.domain.event;
// Reflection for serialization and ORM
opens com.enterprise.domain.model to
com.fasterxml.jackson.databind,
org.hibernate.orm,
spring.core;
// SPI for domain extensions
exports com.enterprise.domain.spi;
}
// Persistence module - data access
module com.enterprise.persistence {
requires transitive com.enterprise.domain;
requires java.sql;
requires java.persistence;
requires transitive org.hibernate.orm;
exports com.enterprise.persistence.repository;
exports com.enterprise.persistence.dao;
opens com.enterprise.persistence.entity to
org.hibernate.orm,
spring.data.jpa;
uses com.enterprise.domain.spi.RepositoryProvider;
}
// Service module - business logic
module com.enterprise.service {
requires transitive com.enterprise.domain;
requires transitive com.enterprise.persistence;
requires java.transaction;
requires static spring.context;
exports com.enterprise.service.facade;
exports com.enterprise.service.manager;
opens com.enterprise.service.impl to
spring.core,
spring.context;
}
// Web module - REST API
module com.enterprise.web {
requires transitive com.enterprise.service;
requires java.net.http;
requires jaxrs.api;
requires static spring.web;
requires static jackson.databind;
exports com.enterprise.web.controller;
exports com.enterprise.web.dto;
opens com.enterprise.web.controller to
spring.web,
jaxrs.api;
opens com.enterprise.web.dto to
jackson.databind,
spring.web;
}
Framework Integration Example
// Spring Boot application module
module com.example.bootapp {
requires java.base;
requires static spring.boot;
requires static spring.boot.autoconfigure;
requires static spring.context;
requires static spring.web;
requires static spring.data.jpa;
requires transitive com.example.domain;
requires transitive com.example.service;
// Export main application class
exports com.example.bootapp;
// Open everything for Spring Boot reflection
opens com.example.bootapp to
spring.core,
spring.context,
spring.beans,
spring.boot;
opens com.example.bootapp.config to
spring.core,
spring.context;
opens com.example.bootapp.controller to
spring.web,
spring.core;
// Open domain entities for JPA
opens com.example.domain.model to
spring.data.jpa,
org.hibernate.orm,
jackson.databind;
// Open service layer for dependency injection
opens com.example.service.impl to
spring.core,
spring.context;
uses org.springframework.boot.SpringApplication;
}
6. Advanced Patterns and Techniques
Optional Dependencies Pattern
// Module with multiple optional features
module com.example.pluggable.features {
requires java.base;
// Core functionality
exports com.example.pluggable.core;
// Optional features
requires static com.example.feature.email;
requires static com.example.feature.sms;
requires static com.example.feature.push;
// Service loader for feature detection
uses com.example.pluggable.spi.FeatureProvider;
}
// Service interface for optional features
package com.example.pluggable.spi;
public interface FeatureProvider {
String getName();
boolean isAvailable();
void execute();
}
// Feature detection and usage
package com.example.pluggable.core;
public class FeatureManager {
public void executeAvailableFeatures() {
ServiceLoader<FeatureProvider> loader =
ServiceLoader.load(FeatureProvider.class);
for (FeatureProvider feature : loader) {
if (feature.isAvailable()) {
try {
feature.execute();
} catch (Exception e) {
// Log but continue with other features
System.err.println("Feature failed: " + feature.getName());
}
}
}
}
}
Module Configuration Pattern
// Configurable module with different profiles
module com.example.configurable.app {
requires java.base;
requires static com.example.config.profile.dev;
requires static com.example.config.profile.prod;
requires static com.example.config.profile.test;
exports com.example.configurable.app.api;
opens com.example.configurable.app.config to
spring.core,
spring.context;
}
// Configuration resolver
public class ProfileResolver {
private static final String ACTIVE_PROFILE =
System.getProperty("app.profile", "dev");
public static boolean isProfileActive(String profile) {
Module module = ProfileResolver.class.getModule();
switch (profile) {
case "dev":
return module.canRead(
ModuleLayer.boot().findModule("com.example.config.profile.dev").orElse(null));
case "prod":
return module.canRead(
ModuleLayer.boot().findModule("com.example.config.profile.prod").orElse(null));
case "test":
return module.canRead(
ModuleLayer.boot().findModule("com.example.config.profile.test").orElse(null));
default:
return false;
}
}
}
7. Migration and Compatibility
Automatic Module Migration
// Before: Classpath-based JARs
// After: Automatic modules
// Non-modular JARs become automatic modules
// JAR name: my-library-1.0.0.jar â module name: my.library
module com.example.migrating.app {
// Automatic modules from legacy JARs
requires commons.lang;
requires guava;
requires slf4j.api;
// Regular modules
requires java.sql;
requires com.example.framework;
exports com.example.migrating.app;
// Open for reflection (common in migration)
opens com.example.migrating.app.model;
opens com.example.migrating.app.dto;
}
Mixed Mode Configuration
// module-info.java for incremental migration
module com.example.incremental.migration {
// Start with minimal requires
requires java.base;
// Add dependencies as they become modularized
requires java.sql;
requires java.logging;
// Automatic modules for non-modularized dependencies
requires legacy.database.driver;
requires old.validation.framework;
// Export only what's necessary
exports com.example.incremental.migration.api;
// Open packages for reflection during migration
opens com.example.incremental.migration.entity;
opens com.example.incremental.migration.dto;
opens com.example.incremental.migration.util;
// Gradually reduce opens as code is refactored
}
8. Troubleshooting and Common Issues
Common Problems and Solutions
// Problem: IllegalAccessError at runtime
module com.example.problem.app {
requires java.base;
exports com.example.problem.app.api;
// Missing: opens com.example.problem.app.model for reflection
}
// Solution: Add opens directive
module com.example.solution.app {
requires java.base;
exports com.example.solution.app.api;
opens com.example.solution.app.model to
spring.core,
hibernate.core;
}
// Problem: Module not found
module com.example.dependency.issue {
requires java.base;
requires non.existent.module; // Compilation error
}
// Solution: Check module names and dependencies
module com.example.dependency.fixed {
requires java.base;
requires correct.module.name;
requires com.valid.module.name;
}
Diagnostic Tools and Techniques
// Module inspection utility
public class ModuleInspector {
public static void printModuleInfo(Class<?> clazz) {
Module module = clazz.getModule();
System.out.println("Module: " + module.getName());
System.out.println("Named: " + module.isNamed());
System.out.println("Exports: " + module.getPackages());
// Check readability
module.getLayer().configuration().modules().forEach(m -> {
if (module.canRead(m)) {
System.out.println("Reads: " + m.name());
}
});
}
public static void checkReflectionAccess(Class<?> targetClass) {
try {
targetClass.getDeclaredFields();
System.out.println("Reflection access allowed for: " + targetClass);
} catch (InaccessibleObjectException e) {
System.out.println("Reflection access denied for: " + targetClass);
System.out.println("Package opened: " +
targetClass.getModule().isOpen(targetClass.getPackageName()));
}
}
}
Key Directives Summary
| Directive | Purpose | Usage Example |
|---|---|---|
requires | Module dependency | requires java.sql; |
requires transitive | Transitive dependency | requires transitive com.example.core; |
requires static | Optional dependency | requires static java.xml; |
exports | Public API | exports com.example.api; |
exports to | Qualified export | exports com.example.internal to framework; |
opens | Reflection access | opens com.example.model; |
opens to | Qualified opens | opens com.example.entity to hibernate; |
These directives provide fine-grained control over module dependencies, API exposure, and reflection access, enabling strong encapsulation and better modular architecture in Java applications.