google-site-verification: google61fe8ba583a51912.html
Java Module System (JPMS) Introduction

The Java Platform Module System (JPMS), introduced in Java 9, provides a new way to package and organize Java applications and libraries. It addresses issues like the "JAR hell" and improves application security, maintainability, and performance.

1. Module System Basics

What is a Module?

A module is a self-contained unit of code that:

  • Has a unique name
  • Declares its dependencies
  • Exports specific packages
  • Hides implementation details

Key Benefits

  • Strong Encapsulation - Explicit control over what's exposed
  • Reliable Configuration - Explicit dependencies
  • Improved Security - Better access control
  • Better Performance - Faster startup and smaller footprint
  • Scalability - Only required modules are loaded

2. Module Descriptor (module-info.java)

Basic Module Structure

my-module/ ├── src/ │ └── module-info.java │ └── com/ │ └── example/ │ └── MyClass.java └── out/ └── my-module.jar

Simple Module Declaration

// module-info.java module com.example.mymodule { // Module declaration body }

3. Module Directives

requires - Declaring Dependencies

module com.example.app { // Required modules requires java.base; // Implicitly required by all modules requires java.sql; requires java.logging; // Optional dependency (not resolved at compile time) requires static java.xml; // Transitive dependency (available to users of this module) requires transitive java.desktop; }

exports - Exposing Packages

module com.example.library { // Export specific packages exports com.example.library.api; exports com.example.library.utils; // Export to specific modules only exports com.example.library.internal to com.example.friend; // Package not exported (hidden) // com.example.library.internal.impl - not accessible outside }

opens - Reflection Access

module com.example.reflective { // Open package for reflection (all modules) opens com.example.reflective.entities; // Open package to specific modules only opens com.example.reflective.internal to spring.core, hibernate; // Open entire module for reflection open module com.example.openmodule { requires java.sql; exports com.example.openmodule.api; } }

uses and provides - Service Loading

module com.example.service.consumer { // Declare service dependency uses com.example.spi.TranslationService; requires com.example.spi; } module com.example.service.provider { // Provide service implementation provides com.example.spi.TranslationService with com.example.provider.EnglishTranslationService, com.example.provider.SpanishTranslationService; requires com.example.spi; exports com.example.provider; }

4. Complete Examples

Example 1: Simple Application Module

// module-info.java module com.example.contacts { requires java.base; requires java.sql; requires java.logging; requires transitive java.desktop; exports com.example.contacts.api; exports com.example.contacts.model; opens com.example.contacts.entities to hibernate.core; uses com.example.contacts.spi.StorageService; }

Corresponding Package Structure

// com/example/contacts/api/ContactService.java package com.example.contacts.api; import com.example.contacts.model.Contact; import java.util.List; public class ContactService { public List<Contact> findAll() { return List.of( new Contact("John", "[email protected]"), new Contact("Jane", "[email protected]") ); } } // com/example/contacts/model/Contact.java package com.example.contacts.model; public class Contact { private final String name; private final String email; public Contact(String name, String email) { this.name = name; this.email = email; } // Getters public String getName() { return name; } public String getEmail() { return email; } } // com/example/contacts/internal/DatabaseHelper.java package com.example.contacts.internal; // This class is not exported - internal implementation class DatabaseHelper { void connect() { // Database connection logic } }

Example 2: Multi-Module Application

Module 1: Domain Layer

// domain/module-info.java module com.example.domain { exports com.example.domain.model; exports com.example.domain.repository; // Service provider interface exports com.example.domain.spi; }
// domain/com/example/domain/model/User.java package com.example.domain.model; public class User { private final String username; private final String email; public User(String username, String email) { this.username = username; this.email = email; } public String getUsername() { return username; } public String getEmail() { return email; } } // domain/com/example/domain/repository/UserRepository.java package com.example.domain.repository; import com.example.domain.model.User; import java.util.Optional; public interface UserRepository { Optional<User> findByUsername(String username); void save(User user); } // domain/com/example/domain/spi/IdGenerator.java package com.example.domain.spi; public interface IdGenerator { String generate(); }

Module 2: Persistence Layer

// persistence/module-info.java module com.example.persistence { requires transitive com.example.domain; requires java.sql; provides com.example.domain.repository.UserRepository with com.example.persistence.jpa.JpaUserRepository; provides com.example.domain.spi.IdGenerator with com.example.persistence.uuid.UuidGenerator; exports com.example.persistence.config; }
// persistence/com/example/persistence/jpa/JpaUserRepository.java package com.example.persistence.jpa; import com.example.domain.model.User; import com.example.domain.repository.UserRepository; import java.util.Optional; public class JpaUserRepository implements UserRepository { @Override public Optional<User> findByUsername(String username) { // JPA implementation return Optional.of(new User(username, username + "@example.com")); } @Override public void save(User user) { // Save to database System.out.println("Saving user: " + user.getUsername()); } } // persistence/com/example/persistence/uuid/UuidGenerator.java package com.example.persistence.uuid; import com.example.domain.spi.IdGenerator; import java.util.UUID; public class UuidGenerator implements IdGenerator { @Override public String generate() { return UUID.randomUUID().toString(); } }

Module 3: Application Layer

// application/module-info.java module com.example.application { requires com.example.domain; requires com.example.persistence; requires java.logging; uses com.example.domain.repository.UserRepository; uses com.example.domain.spi.IdGenerator; exports com.example.application.service; }
// application/com/example/application/service/UserService.java package com.example.application.service; import com.example.domain.model.User; import com.example.domain.repository.UserRepository; import com.example.domain.spi.IdGenerator; import java.util.ServiceLoader; import java.util.logging.Logger; public class UserService { private static final Logger LOG = Logger.getLogger(UserService.class.getName()); private final UserRepository userRepository; private final IdGenerator idGenerator; public UserService() { // Load services using ServiceLoader this.userRepository = ServiceLoader.load(UserRepository.class) .findFirst() .orElseThrow(() -> new IllegalStateException("No UserRepository implementation found")); this.idGenerator = ServiceLoader.load(IdGenerator.class) .findFirst() .orElseThrow(() -> new IllegalStateException("No IdGenerator implementation found")); } public void registerUser(String username, String email) { String userId = idGenerator.generate(); User user = new User(username, email); userRepository.save(user); LOG.info("Registered user: " + username + " with ID: " + userId); } public User findUser(String username) { return userRepository.findByUsername(username) .orElseThrow(() -> new IllegalArgumentException("User not found: " + username)); } }

5. Migration Strategies

Automatic Modules (Migration Path)

// When migrating legacy JARs without module-info.java // They become automatic modules module com.example.migrating.app { // Legacy JARs become automatic modules requires commons.lang; // From commons-lang-3.12.0.jar requires guava; // From guava-31.0.jar requires log4j; // From log4j-core-2.17.1.jar requires java.sql; }

Example: Migrating Spring Application

// module-info.java for Spring Boot application open module com.example.springapp { requires spring.boot; requires spring.boot.autoconfigure; requires spring.context; requires spring.web; requires spring.data.commons; requires java.persistence; requires java.sql; requires java.validation; // Open for Spring reflection opens com.example.springapp to spring.core, spring.beans, spring.context; opens com.example.springapp.controllers to spring.web; opens com.example.springapp.entities to spring.core, hibernate; opens com.example.springapp.repositories to spring.data.commons; exports com.example.springapp.controllers; exports com.example.springapp.services; }

6. Building and Running Modules

Command Line Compilation

# Compile multiple modules javac -d out \ --module-source-path src \ --module com.example.domain,com.example.persistence,com.example.application # Or compile individually javac -d out/domain \ src/com.example.domain/module-info.java \ src/com.example.domain/com/example/domain/**/*.java javac -d out/persistence \ --module-path out \ --module-source-path src \ --module com.example.persistence

Packaging Modules

# Create modular JARs jar --create --file lib/domain.jar -C out/domain . jar --create --file lib/persistence.jar -C out/persistence . jar --create --file lib/application.jar -C out/application . # Run the application java --module-path lib:mods -m com.example.application/com.example.application.Main

Using jlink to Create Custom Runtime

# Create custom runtime image jlink --module-path $JAVA_HOME/jmods:lib \ --add-modules com.example.application \ --launcher myapp=com.example.application/com.example.application.Main \ --output myruntime # Run from custom runtime myruntime/bin/myapp

7. Advanced Module Features

Module Resolution and Layers

// Creating custom module layers public class ModuleLayerExample { public static void main(String[] args) throws Exception { ModuleFinder finder = ModuleFinder.of(Paths.get("plugins")); ModuleLayer parent = ModuleLayer.boot(); Configuration config = parent.configuration() .resolve(finder, ModuleFinder.of(), Set.of("com.example.plugin")); ClassLoader scl = ClassLoader.getSystemClassLoader(); ModuleLayer layer = parent.defineModulesWithOneLoader(config, scl); // Use plugin Class<?> pluginClass = layer.findLoader("com.example.plugin") .loadClass("com.example.plugin.MyPlugin"); } }

Qualified Exports and Opens

module com.example.secure { // Fine-grained access control exports com.example.secure.api to com.example.trusted; opens com.example.secure.internal to com.example.trusted, spring.core; // Conditional exports based on module names }

Service Loader in Modules

// Advanced service loading with modules module com.example.plugin.host { uses com.example.spi.Plugin; // Dynamically load plugins public class PluginManager { public List<Plugin> loadPlugins() { return ServiceLoader.load(Plugin.class) .stream() .map(Provider::get) .collect(Collectors.toList()); } } }

8. Testing with Modules

Test Module Structure

// src/test.modules/com.example.test/module-info.java open module com.example.test { requires com.example.application; requires org.junit.jupiter; requires org.mockito; opens com.example.application.test to org.junit.platform.commons; // Read reflection for testing opens com.example.application.service to org.mockito; }

JUnit 5 Test in Module

package com.example.application.test; import com.example.application.service.UserService; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.BeforeEach; import static org.junit.jupiter.api.Assertions.*; class UserServiceTest { private UserService userService; @BeforeEach void setUp() { userService = new UserService(); } @Test void testUserRegistration() { assertDoesNotThrow(() -> { userService.registerUser("testuser", "[email protected]"); }); } }

9. Common Patterns and Best Practices

1. Module Naming

// Use reverse DNS naming module com.company.product.feature { // Avoid: module feature, module mymodule }

2. Layer Separation

// Clear separation of concerns module com.example.app.domain { } module com.example.app.persistence { } module com.example.app.service { } module com.example.app.web { }

3. API Module Pattern

// api/module-info.java module com.example.library.api { exports com.example.library.api; exports com.example.library.spi; } // implementation/module-info.java module com.example.library.impl { requires transitive com.example.library.api; provides com.example.library.spi.Service with com.example.library.impl.ServiceImpl; }

4. Optional Dependencies

module com.example.configurable { requires static com.example.optional.feature; public class FeatureManager { public void useFeature() { // Check if optional module is available boolean hasFeature = ModuleLayer.boot() .findModule("com.example.optional.feature") .isPresent(); if (hasFeature) { // Use optional feature } } } }

10. Troubleshooting Common Issues

Module Resolution Problems

// Common error: module not found module com.example.app { requires non.existent.module; // Error: module not found } // Solution: Check module names and module path

Split Packages Issue

// Error: Split package between modules // Module A: exports com.example.util // Module B: exports com.example.util // Solution: Refactor packages or use jdeps to analyze

Reflection Access Denied

// Error: Unable to make field accessible module com.example.reflective { // Solution: Add opens directive opens com.example.reflective.entities; }

11. Tooling Support

jdeps - Dependency Analysis

# Analyze dependencies jdeps -s --module-path lib application.jar # Check for unused dependencies jdeps --api-only --module-path lib application.jar # Generate module-info.java for migration jdeps --generate-module-info ./out legacy.jar

jmod - Module Packaging

# Create JMOD files jmod create --class-path out/module.jar module.jmod # List JMOD contents jmod list module.jmod

jlink - Custom Runtime

# Create minimal runtime jlink --module-path $JAVA_HOME/jmods:mods \ --add-modules java.base,java.logging,com.example.app \ --compress=2 \ --no-header-files \ --no-man-pages \ --output minimal-runtime

12. Real-World Example: Microservice Module

Microservice Module Structure

// module-info.java for microservice open module com.example.userservice { requires spring.boot; requires spring.boot.autoconfigure; requires spring.web; requires spring.data.jpa; requires spring.security.core; requires java.persistence; requires java.validation; requires java.sql; requires com.fasterxml.jackson.databind; requires transitive com.example.common; // Spring reflection access opens com.example.userservice to spring.core, spring.beans, spring.context; opens com.example.userservice.controllers to spring.web, spring.security; opens com.example.userservice.entities to spring.core, hibernate; opens com.example.userservice.repositories to spring.data.commons; opens com.example.userservice.security to spring.security; exports com.example.userservice.controllers; exports com.example.userservice.services; exports com.example.userservice.dto; }

Summary

The Java Module System provides:

  • Strong encapsulation through explicit exports
  • Reliable dependencies with requires directives
  • Service abstraction with uses/provides
  • Reflection control with opens directives
  • Better tooling with jdeps, jlink, jmod

Migration path:

  1. Start with automatic modules
  2. Add module-info.java to libraries
  3. Use jdeps for dependency analysis
  4. Create custom runtimes with jlink

JPMS enables building more secure, maintainable, and performant Java applications while maintaining backward compatibility with existing code.

Leave a Reply

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


Macro Nepal Helper