Java Modules (Project Jigsaw)
Introduced in Java 9, the Java Platform Module System (JPMS), widely known as Project Jigsaw, fundamentally changed how Java applications and the JDK itself are structured. Before modules, Java applications relied on a flat classpath, leading to "JAR hell," classpath collisions, and lack of strong encapsulation across JAR boundaries.
1. What is a Module?
A module is a cohesive group of related packages and resources, along with a new module descriptor file (module-info.java). It explicitly dictates:
- Name: A unique identifier for the module.
- Dependencies: What other modules it requires to compile and run (
requires). - Exports: Which of its internal packages are accessible to the outside world (
exports). Packages not explicitly exported are strongly encapsulated (hidden) even if they containpublicclasses.
2. Core Concepts and Keywords
The module descriptor (module-info.java) sits at the root of the source directory.
requires
Specifies a dependency on another module.
module com.myapp.core {
requires java.sql; // Requires the standard SQL module
requires org.slf4j; // Requires an external logging module
}
exports
Makes public types in a specific package accessible to other modules.
module com.myapp.utils {
exports com.myapp.utils.strings; // Now visible to modules that require this module
// Packages not listed here are strictly private to this module
}
requires transitive
If Module A requires Module B, and Module B requires transitive Module C, then Module A automatically gets access to Module C.
module com.myapp.database {
requires transitive java.sql; // Any module requiring database also gets java.sql
}
exports ... to (Qualified Exports)
Exports a package, but only to specifically named modules, providing tight security.
module com.myapp.security {
exports com.myapp.security.internal to com.myapp.admin;
}
provides ... with & uses (Services)
JPMS heavily supports the Service Locator pattern.
uses: Declares that the module consumes a service interface.provides ... with: Declares that the module provides an implementation for an interface.
// Consumer module
module com.myapp.app {
uses com.myapp.spi.MessageService;
}
// Provider module
module com.myapp.provider {
requires com.myapp.app;
provides com.myapp.spi.MessageService with com.myapp.provider.EmailService;
}
3. Benefits of the Module System
1. Reliable Configuration
In the pre-module era, missing JARs were only discovered at runtime (NoClassDefFoundError). With JPMS, if a required module is missing, the JVM will fail immediately at startup (or during compilation).
2. Strong Encapsulation
Before Java 9, a public class in a JAR was accessible to everyone on the classpath. Modules allow you to have public classes that are only accessible within your module. This prevents users of your library from relying on internal implementation details.
3. Scalable Java Platform
The JDK itself was modularized into around 90 modules (e.g., java.base, java.desktop, java.sql). This means you no longer need to ship a massive JRE with your application.
4. Custom Runtime Images (jlink)
Because the JVM knows exactly which modules your application needs, you can use the jlink tool to create a custom, stripped-down Java Runtime Environment that contains only the parts of the JDK your app actually uses. This drastically reduces Docker image sizes and memory footprints for microservices.
4. The Unnamed Module and Automatic Modules
To maintain backward compatibility, Java 9 did not force everyone to rewrite their applications overnight.
- The Unnamed Module: Any JAR placed on the traditional
--classpath(instead of the--module-path) belongs to the Unnamed Module. It exports all its packages and reads all other modules. - Automatic Modules: If you place a non-modular JAR (a JAR without
module-info.java) on the--module-path, it becomes an Automatic Module. Its name is derived from the JAR filename, it exports all packages, and it can berequiredby explicit modules.
Summary
Java Modules enforce architectural boundaries at the JVM level. While adopting JPMS in existing legacy monolithic applications can be challenging, it is highly recommended for new library development, SDKs, and applications where footprint and strict encapsulation are a priority.