Requires, Exports, and Opens Directives in Java

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

DirectivePurposeUsage Example
requiresModule dependencyrequires java.sql;
requires transitiveTransitive dependencyrequires transitive com.example.core;
requires staticOptional dependencyrequires static java.xml;
exportsPublic APIexports com.example.api;
exports toQualified exportexports com.example.internal to framework;
opensReflection accessopens com.example.model;
opens toQualified opensopens 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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper