With the release of Java 9, the Java Platform introduced a significant architectural shift: the Java Platform Module System (JPMS). While backward compatibility remains intact, JPMS enables developers to write better organized, secure, and scalable applications.
In this blog post, we will explore:
- What JPMS is and why it was introduced
- Key concepts like modules,
module-info.java, readability and accessibility - How to migrate existing code to a modular structure
- Practical use cases in enterprise and microservices environments
- Best practices for adopting JPMS in modern Java projects
Why JPMS Was Introduced
Before Java 9, all Java code lived in a flat classpath. This often led to:
- Classpath Hell: Conflicting versions of libraries, ambiguous dependencies.
- Poor Encapsulation: All public classes were accessible to everyone.
- Lack of Scalability: Difficulty trimming unused libraries for smaller footprints.
To solve these, JPMS brought in:
- Explicit Dependency Management
- Strong Encapsulation
- Smaller Runtime Images (via
jlink)
Core Concepts of JPMS
1. Module
A self-describing collection of packages and resources. Each module must declare itself with a module-info.java file.
javaCopyEditmodule com.example.myapp {
requires java.sql;
exports com.example.myapp.services;
}
2. requires Clause
Declares dependencies on other modules.
javaCopyEditrequires java.base;
requires com.google.gson;
3. exports Clause
Exposes specific packages to other modules.
javaCopyEditexports com.example.api;
4. Strong Encapsulation
Only explicitly exported packages are accessible. Internal classes remain truly private, even with public modifier.
5. Readability vs Accessibility
- Readability: Module A “reads” Module B if it declares
requires. - Accessibility: Only types in exported packages of B are accessible to A.
Migrating Existing Projects to JPMS
Here’s a high-level plan for modularizing an existing Java application:
Step 1: Identify Logical Boundaries
Break your app into meaningful units like core, api, service, persistence.
Step 2: Create module-info.java in Each Module
This defines what the module provides and consumes.
Step 3: Restructure Your Build Tool
For Maven, use:
xmlCopyEdit<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>17</release>
</configuration>
</plugin>
</plugins>
</build>
Step 4: Compile with --module-path
bashCopyEditjavac --module-path mods -d out --module com.example.myapp
Practical Use Cases
Enterprise Systems
Modularizing allows large teams to work independently across features while enforcing boundaries.
Microservices
With jlink, you can generate lean Java runtimes with only required modules, improving startup time and memory use.
Libraries
Library developers can expose only the public APIs and hide internal logic, increasing maintainability and reducing accidental usage.
Tooling Support
- JDeps: Analyzes dependencies
- JLink: Creates custom runtime images
- JMod: Creates JDK-style module artifacts
- Jigsaw: The reference implementation of JPMS
Limitations and Gotchas
- Framework Compatibility: Some reflection-heavy libraries may break (Spring Boot handles this via
opens). - Steep Learning Curve: Especially in polyglot environments with older libraries.
- Split Packages: Not allowed—refactor or combine packages into a single module.
Best Practices
- Start with clear module boundaries.
- Minimize use of
opensandreflective access. - Use JPMS incrementally—start with internal modules.
- Keep module definitions consistent across environments (build, CI/CD, deployment).
Summary
JPMS marks a foundational shift in how Java applications are structured and deployed. By adopting it, developers gain better encapsulation, dependency clarity, and runtime optimization.
While not every application needs full modularization today, understanding JPMS prepares you for future-ready, scalable Java development.
0 Comments