Packages & Modules
Modules (Java 9+)
Master the Java Platform Module System (JPMS), module-info.java declarations, and strong encapsulation directives.
Interview: Highly tested: modular directives (requires, exports, opens), reflection boundaries (opens vs exports), and differences between modular and classpath code.
The Java Platform Module System (JPMS), introduced in Java 9, provides a modular framework for the Java SE platform. By grouping packages into self-describing modules, JPMS enforces strong encapsulation, reliable dependencies, and improved startup times.
Reliable Dependencies
Replaces classpath scanning with modular configuration requirements validated by the JVM during startup, eliminating lazily thrown exceptions.
Strong Encapsulation
Classes in non-exported packages are completely hidden. Access is blocked at compile and runtime, even via reflection.
module-info.java
The descriptor file located at the root of the source tree. It declares the module name, dependencies, and exports.
Modular Directives in JPMS
A module defines its relationships with other modules using specific directives in its module-info.java descriptor:
requires <module-name>;: Declares a dependency on another module. If the required module is missing at startup, the JVM fails immediately.exports <package-name>;: Exposes all public classes and interfaces in the specified package to other modules.opens <package-name>;: Permits runtime reflection (e.g. for serialization, dependency injection) on the package's classes, while keeping compile-time access blocked.
Exports vs. Opens
Choosing the correct directive is critical for balancing encapsulation with framework integration:
- exports: Allows other modules to compile against the package's public API. It is used for your module's public entry points.
- opens: Blocks compile-time access but allows reflection frameworks (like Spring, Jackson, or Hibernate) to read and modify fields (even private ones) at runtime.
Common Pitfalls
- Encountering IllegalAccessError on Reflection: Forgetting to open packages containing DTOs or entities when using reflection-heavy frameworks like Jackson, leading to runtime failures.
- Circular Dependencies: Declaring circular dependencies between modules (e.g., Module A requires Module B, and Module B requires Module A), which is prohibited by the compiler.
Best Practices
- Encapsulate Internal Packages: Only export packages that define public APIs. Keep implementation-specific packages hidden.
- Open Selectively: Use target openings (e.g.
opens com.company.dto to com.fasterxml.jackson;) to restrict reflection access to specific frameworks instead of opening them globally.
Interview-Relevant Information
Q1: What is the difference between exports and opens in module-info.java?
Answer: exports allows other modules to access a package's public classes at both compile and runtime. opens blocks compile-time access to the package but permits reflection (even on private members) at runtime, which is required by frameworks like Spring and Hibernate.
Q2: What is the unnamed module in Java 9+?
Answer: For backward compatibility, classes loaded from the Classpath are placed in the unnamed module. The unnamed module can access all exported packages of other modules, allowing legacy applications to run on modern modular JVMs without modification.
Quick Checklist
Can you explain JPMS strong encapsulation, write a valid module-info.java file using requires and exports directives, and contrast exports with opens? If yes, you understand Modules in Java.
Use Cases
Enforcing architectural boundaries in microservices monorepos.
Optimizing application sizes by using jlink to build custom runtimes containing only required modules.
Common Mistakes
Failing to open model packages for serialization frameworks, causing IllegalAccessErrors at runtime.
Creating circular dependencies between modules, resulting in compile-time failures.