☕ Java Q75 / 162

Explain Java modules system (JPMS).

AI-Powered Answer ✓ Answered

The Java Platform Module System (JPMS), introduced in Java 9, is a significant architectural change that aims to improve the scalability, security, and maintainability of Java applications and the JDK itself. It provides a mechanism to encapsulate code and data into modules, making dependencies explicit and enforcing strong encapsulation.

What is a Module?

At its core, a module is a named, self-describing collection of code and data. It groups related packages, types, and resources, and explicitly declares which of those packages it makes available to other modules (exports) and which modules it depends on (requires).

Key characteristics of a module include strong encapsulation, explicit dependencies, and reliable configuration. This stands in contrast to the traditional classpath, where all JARs are dumped into a flat structure, leading to issues like 'classpath hell'.

The `module-info.java` File

Every module is defined by a module descriptor file named module-info.java. This file, located in the root of the module's source directory, specifies the module's name, its dependencies, and the packages it exports. It's compiled into module-info.class and packaged with the module.

java
module com.example.myapp {
    requires java.base;
    requires com.example.mylibrary;
    exports com.example.myapp.api;
    opens com.example.myapp.internal to com.example.framework;
}

module com.example.myapp { ... } declares a module named com.example.myapp.

requires java.base; declares a dependency on the java.base module (which is implicitly required by all modules anyway, but shown for clarity). requires com.example.mylibrary; makes all exported packages of com.example.mylibrary available to com.example.myapp.

exports com.example.myapp.api; makes all public types in the com.example.myapp.api package accessible to other modules that require com.example.myapp.

opens com.example.myapp.internal to com.example.framework; allows types in com.example.myapp.internal to be accessed reflectively at runtime by the com.example.framework module, even though they are not exported.

Key Directives in `module-info.java`

  • module <module_name> { ... }: Declares a named module.
  • requires <other_module_name>;: Declares a readability dependency. The current module needs other_module_name to compile and run.
  • requires transitive <other_module_name>;: Similar to requires, but also makes other_module_name readable to any module that reads the current module. This propagates the dependency.
  • exports <package_name>;: Makes all public types within <package_name> accessible to other modules that depend on this module.
  • exports <package_name> to <target_module_name>;: A qualified export, making <package_name> accessible only to the specified target_module_name.
  • opens <package_name>;: Allows *runtime* reflective access to all public and non-public types within <package_name> by any module. The package is not exported for compile-time access.
  • opens <package_name> to <target_module_name>;: A qualified open, allowing runtime reflective access only by target_module_name.
  • uses <service_interface_name>;: Declares that this module uses a service provided by another module, specified by the service interface or abstract class.
  • provides <service_interface_name> with <service_implementation_class>;: Declares that this module provides an implementation for the specified service interface.

Benefits of JPMS

  • Strong Encapsulation: Hides internal implementation details by default, exposing only explicitly exported APIs. This prevents accidental misuse of internal code and facilitates refactoring without breaking dependents.
  • Reliable Configuration: The module system verifies dependencies at compile time and launch time, ensuring that all required modules are present and compatible. This eliminates many 'classpath hell' issues.
  • Improved Performance: Allows the JVM to optimize class loading and reduce memory footprint by loading only necessary modules and performing static linking for smaller runtimes (e.g., using jlink).
  • Scalability and Maintainability: Easier to manage large applications by breaking them into smaller, independent, and well-defined modules with clear interfaces.
  • Enhanced Security: Restricts access to internal APIs, reducing the attack surface and making applications more secure.

Module Path vs. Class Path

FeatureClass PathModule Path
StructureFlat list of JARs/directoriesHierarchical, named modules
EncapsulationNone (all public classes visible)Strong (only exported packages visible)
Dependency MgmtRuntime resolution (Classpath Hell)Compile-time & Launch-time verification
Service LoadingManual (ServiceLoader)Automated (`uses`/`provides` directives)
JAR ContentArbitraryMust contain module descriptor (`module-info.java`)

Building and Running Modular Applications

When compiling and running modular applications, you use the --module-path (or -p) option instead of --classpath (or -cp). The module path specifies the directories containing compiled modules or modular JARs.

bash
# Compile a module
javac -d mods/com.example.myapp --module-path mods \ 
      src/com.example.myapp/module-info.java src/com.example.myapp/com/example/myapp/api/*.java
bash
# Run a modular application
java --module-path mods -m com.example.myapp/com.example.myapp.Main

Conclusion

The Java Platform Module System represents a fundamental shift in how Java applications are structured and managed. By enforcing strong encapsulation and explicit dependencies, JPMS brings a new level of reliability, security, and performance to the Java ecosystem, making it easier to build and maintain complex systems.