10.1 Module Architecture and module-info.java Declarations
Key Takeaways
Project Jigsaw restructured the monolithic JDK standard library and runtime into discrete, cohesive modules to enforce strong encapsulation, reliable configuration, and minimal runtime footprints.
The foundational module java.base is implicitly required by every Java module without explicit declaration, exporting essential platform packages including java.lang, java.util, and java.io.
A module descriptor is defined in a root-level module-info.java file using contextual (restricted) keywords like module, open, requires, and exports, which remain valid variable and method identifiers in standard Java code.
Access in JPMS requires both readability (module-to-module graph relationship established via requires) and accessibility (type/member level visibility in an exported package).
An open module permits deep reflective access at runtime to all its private and public types for all other modules, whereas standard named modules strictly encapsulate all unexported and non-opened packages against both compilation and reflection.
Module Architecture and module-info.java Declarations
Prior to Java 9, Java applications and the Java Virtual Machine itself suffered from architectural limitations rooted in a flat, unencapsulated runtime model. The Java Platform Module System (JPMS), developed under Project Jigsaw (JSR 376) and codified in Java SE 9 through Java SE 21, established a standardized modular system for the Java platform and application code. For the Oracle Certified Professional: Java SE 21 Developer (1Z0-830) exam, understanding module descriptors, encapsulation boundaries, contextual keywords, readability versus accessibility, and the special status of java.base is foundational.
1. Project Jigsaw Motivations and Core Goals
For over two decades, Java applications relied on two primary deployment and execution mechanisms: the Classpath and monolithic archive files like rt.jar (the runtime JAR file containing the entire standard Java library, often exceeding 60 MB). This legacy architecture exhibited several critical flaws:
- Classpath Fragility ("JAR Hell" / "Classpath Hell"):
- The runtime classpath is an ordered list of directories and JAR archives. If a required class is missing, the JVM fails at runtime with a
NoClassDefFoundErrororClassNotFoundExceptiononly when that specific class is first referenced during execution. - If two JAR files contain classes with identical fully qualified names (package and class name), the JVM arbitrarily loads whichever class appears earlier in the classpath string, causing silent runtime incompatibilities and non-deterministic bugs.
- The runtime classpath is an ordered list of directories and JAR archives. If a required class is missing, the JVM fails at runtime with a
- Weak Encapsulation Across Package Boundaries:
- Standard Java access modifiers (
public,protected, package-private, andprivate) lacked a mechanism to expose apublicclass to helper packages within the same library without exposing it to all external consumers on the classpath. - Developers frequently accessed unstable internal implementation APIs (such as
sun.misc.Unsafeorcom.sun.*), creating tight coupling to specific JDK vendor internals that hindered JVM evolution and security hardening.
- Standard Java access modifiers (
- Monolithic JDK Runtime Footprint:
- Small devices, embedded hardware, and microservice cloud containers were forced to bundle the entire Java standard library, including desktop GUI stacks (
java.desktop/ AWT / Swing) and legacy enterprise packages, even if only basic console or HTTP logging functionality was needed.
- Small devices, embedded hardware, and microservice cloud containers were forced to bundle the entire Java standard library, including desktop GUI stacks (
Core JPMS Architectural Goals
- Reliable Configuration: Modules explicitly declare their dependencies on other modules. The JVM verifies the presence and consistency of the entire dependency graph at startup, failing immediately with descriptive errors if dependencies are missing or if cyclic dependencies exist.
- Strong Encapsulation: A package in a module is completely hidden and inaccessible to other modules unless explicitly exported. Public classes and methods within unexported packages cannot be accessed at compile time, and runtime reflection (
java.lang.reflect) cannot penetrate private members without explicit permission. - Scalable Platform: The JDK standard library itself is partitioned into modular units (
java.base,java.sql,java.logging,java.desktop, etc.), enabling custom, stripped-down runtime images viajlink.
2. Readability vs. Accessibility
One of the most essential conceptual foundations for the 1Z0-830 exam is the distinction between Readability and Accessibility:
+-----------------------------------------------------------------------------+
| READABILITY |
| - Module-to-Module graph relationship |
| - Established when Module A declares 'requires Module B;' |
| - Without readability, Module A cannot resolve ANY types declared in B |
+-----------------------------------------------------------------------------+
+
+-----------------------------------------------------------------------------+
| ACCESSIBILITY |
| - Package and Type/Member visibility relationship |
| - Established when Module B 'exports' package P, AND type T is 'public', |
| AND the target member in T is 'public' (or 'protected' to subclasses) |
+-----------------------------------------------------------------------------+
||
\/
+-----------------------------------------------------------------------------+
| LEGAL CODE ACCESS AT RUNTIME |
| Code in Module A can access type T in package P of Module B if and only |
| if BOTH Readability AND Accessibility are fully satisfied! |
+-----------------------------------------------------------------------------+
The Accessibility Truth Matrix
| Scenario | Module Readability (requires) | Package Exported (exports) | Type Modifier | Resulting Access |
|---|---|---|---|---|
| Case 1 | Module A requires Module B | Module B exports com.b.api | public class Service | Permitted (Compile-time and runtime) |
| Case 2 | Module A requires Module B | Package com.b.internal NOT exported | public class Helper | Blocked (Compile error / IllegalAccessError) |
| Case 3 | Module A does NOT require Module B | Module B exports com.b.api | public class Service | Blocked (Module A cannot read Module B) |
| Case 4 | Module A requires Module B | Module B exports com.b.api | class Secret (package-private) | Blocked (Standard Java access control applies) |
3. The Core Foundation: java.base
The java.base module is the foundational root of the entire Java module graph. It exports core platform packages and possesses unique architectural rules:
- Implicit Readability: Every Java module implicitly requires
java.base. You do not need to declarerequires java.base;in your module descriptor. - Explicit Declaration: Explicitly writing
requires java.base;is syntactically valid and compiles normally; it simply restates the dependency the compiler would have added anyway. Writingrequires transitive java.base;, however, is a compile-time error. - Zero Dependencies:
java.basedoes not require any other module; it sits at the root of the acyclic dependency graph. - Essential Packages Included in
java.base:java.lang,java.lang.invoke,java.lang.reflectjava.util,java.util.concurrent,java.util.function,java.util.streamjava.io,java.nio,java.nio.file,java.nio.channelsjava.math,java.net,java.time,java.time.formatjava.security,java.text
Note
Other standard JDK modules like java.sql (JDBC), java.logging (java.util.logging), java.xml (DOM/SAX parsing), and java.desktop (AWT/Swing) are not in java.base and must be explicitly declared with requires directives when used.
4. Module Descriptors: module-info.java Syntax and Structure
A module is defined by creating a module descriptor file named strictly module-info.java.
File Placement and Compilation Rules
- Root Placement:
module-info.javamust be placed in the root directory of the module's source hierarchy (directly above all package subdirectories), not inside any package. - Compiled Output: Compiling
module-info.javayieldsmodule-info.classlocated at the root of the output classes directory or modular JAR archive. - One Descriptor per Module: Each module contains exactly one
module-info.javadescriptor. - Module Naming Conventions:
- Module names follow reverse-DNS naming conventions (e.g.,
com.oracle.cert.orders,org.example.finance). - Valid characters in module names match standard Java identifier rules joined by periods (
.): letters, numbers, and underscores. - Hyphens (
-) are legal in JAR file names on the file system, but cannot be used in module names insidemodule-info.javabecause hyphens are minus subtraction operators in Java syntax.
- Module names follow reverse-DNS naming conventions (e.g.,
Project Source Tree Structure:
src/
└── com.example.orders/ <-- Module root directory
├── module-info.java <-- Module descriptor at root
└── com/
└── example/
└── orders/
├── OrderProcessor.java
└── model/
└── Order.java
Minimal Module Declaration Example
// In src/com.example.orders/module-info.java
module com.example.orders {
// Implicitly requires java.base;
exports com.example.orders;
exports com.example.orders.model;
}
5. Contextual (Restricted) Keywords in JPMS
To ensure backward compatibility with pre-Java 9 codebases, the tokens used in module-info.java are contextual keywords (also called restricted keywords). They are treated as keywords only inside a module-info.java file.
| Contextual Keyword | Purpose inside module-info.java | Legal as variable/method in standard .java? |
|---|---|---|
module | Declares a named module | Yes (e.g., int module = 10;) |
open | Declares an open module | Yes (e.g., public void open() {}) |
requires | Declares a module dependency | Yes (e.g., boolean requires = true;) |
exports | Exports a package to consumers | Yes (e.g., String exports = "data";) |
opens | Opens a package for reflection | Yes (e.g., int opens = 0;) |
uses | Declares a service interface consumption | Yes (e.g., void uses(int count) {}) |
provides | Declares a service provider implementation | Yes (e.g., int provides = 42;) |
with | Pairs with provides to specify provider class | Yes (e.g., record with() {}) |
to | Restricts exports or opens to target modules | Yes (e.g., String to = "user";) |
transitive | Modifier on requires for implied readability | Yes (e.g., boolean transitive = false;) |
static | Modifier on requires for compile-time optional | Yes (Existing strict keyword) |
// Legal regular Java class demonstrating contextual keyword coexistence
package com.example.demo;
public class ContextualKeywordsDemo {
public static void main(String[] args) {
int module = 101;
String exports = "orders";
boolean requires = true;
System.out.println("Module ID: " + module + ", export: " + exports + ", req: " + requires);
}
public void open(String to, int with) {
// 'open', 'to', and 'with' are valid parameter and method names
}
}
6. Standard Named Modules vs. Open Modules
JPMS strictly enforces encapsulation against both standard compilation/linking and reflective access (java.lang.reflect). However, enterprise frameworks (such as Spring, Hibernate, Jackson, and JUnit) rely heavily on reflection to inspect and mutate private fields, invoke private constructors, and inject dependencies.
Java provides two levels of module open-configuration:
1. Standard Explicit Named Module
Declared with the standard module keyword:
module com.example.service {
exports com.example.service.api;
// Unexported packages are completely encapsulated against compilation AND reflection!
}
- At compile time: Only types in exported packages are accessible.
- At runtime reflection: Calling
Field.setAccessible(true)orMethod.setAccessible(true)on non-public types or unexported packages throwsjava.lang.reflect.InaccessibleObjectException.
2. Open Module (open module)
Declared by prefixing open before module:
open module com.example.entities {
requires java.sql;
exports com.example.entities.dto;
// All packages in this module are automatically open for runtime deep reflection!
}
- Compile-Time Behavior: Public types are accessible to other modules only if explicitly exported using
exports. Unexported packages remain inaccessible tojavacduring compilation. - Runtime Reflection: All packages within the open module are opened for deep reflection to all other modules. Frameworks can invoke
setAccessible(true)on private fields and constructors across the entire module. - Syntax Constraint: An
open modulecannot contain individualopensdirectives inside its body. Attempting to write anopensstatement inside anopen modulecauses a compile-time error.
Module Encapsulation Comparison Matrix
| Capability / Property | Standard Named Module (module) | Open Module (open module) |
|---|---|---|
| Compile-Time Access | Exported packages only | Exported packages only |
| Runtime Standard Access | Exported packages only | Exported packages only |
Runtime Deep Reflection (setAccessible) | Only explicitly opened packages (opens) | All packages across the module |
Allows opens directive in body? | Yes (e.g., opens pkg;) | No (Compile-time error) |
Allows exports directive in body? | Yes | Yes |
| Primary Use Case | Secure business logic, APIs, libraries | Entity modules, domain models, Spring/Hibernate models |
What is the result when a developer explicitly declares requires java.base; inside a module-info.java file?
The module compiles successfully because java.base is implicitly required by every module, making the explicit directive merely redundant
The compiler generates a fatal error because java.base cannot be explicitly referenced in a module descriptor
The JVM fails at startup with a ModuleResolutionException due to duplicate module resolution
The module descriptor becomes invalid, causing the compiler to ignore all subsequent export statements
Which statement accurately describes the behavior of contextual (restricted) keywords in Java SE 21 modular development?
Keywords such as 'module', 'requires', and 'exports' cannot be used as method or variable identifiers anywhere in a Java application
The identifier 'module' is a contextual keyword recognized exclusively inside module-info.java and is completely valid as a variable, method, or package identifier in standard Java source code
Contextual keywords can only be used as variable identifiers if preceded by an escape character
Contextual keywords were deprecated in Java 17 and removed in Java 21
Which statement correctly describes the characteristics and syntax constraints of an open module in JPMS?
An open module exposes all public types for compilation to other modules without requiring explicit exports directives
An open module requires every internal package to be explicitly declared with an opens directive in the descriptor body
Declaring 'open module com.app { ... }' allows runtime deep reflection into all packages in the module, and placing an 'opens' directive inside an open module causes a compile-time error
An open module disables compile-time type checking for all dependent external modules
Where must the module-info.java source file be placed within a module's directory structure?
Inside the META-INF directory alongside the manifest file
Inside the root package directory next to the primary application class
In the parent workspace folder containing all separate module folders
At the root directory of the module source tree, directly above any package directories
Sections you finish are checked off in the contents.