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.
Last updated: September 2026

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:

  1. 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 NoClassDefFoundError or ClassNotFoundException only 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.
  2. Weak Encapsulation Across Package Boundaries:
    • Standard Java access modifiers (public, protected, package-private, and private) lacked a mechanism to expose a public class 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.Unsafe or com.sun.*), creating tight coupling to specific JDK vendor internals that hindered JVM evolution and security hardening.
  3. 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.

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 via jlink.

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

ScenarioModule Readability (requires)Package Exported (exports)Type ModifierResulting Access
Case 1Module A requires Module BModule B exports com.b.apipublic class ServicePermitted (Compile-time and runtime)
Case 2Module A requires Module BPackage com.b.internal NOT exportedpublic class HelperBlocked (Compile error / IllegalAccessError)
Case 3Module A does NOT require Module BModule B exports com.b.apipublic class ServiceBlocked (Module A cannot read Module B)
Case 4Module A requires Module BModule B exports com.b.apiclass 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 declare requires 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. Writing requires transitive java.base;, however, is a compile-time error.
  • Zero Dependencies: java.base does 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.reflect
    • java.util, java.util.concurrent, java.util.function, java.util.stream
    • java.io, java.nio, java.nio.file, java.nio.channels
    • java.math, java.net, java.time, java.time.format
    • java.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

  1. Root Placement: module-info.java must be placed in the root directory of the module's source hierarchy (directly above all package subdirectories), not inside any package.
  2. Compiled Output: Compiling module-info.java yields module-info.class located at the root of the output classes directory or modular JAR archive.
  3. One Descriptor per Module: Each module contains exactly one module-info.java descriptor.
  4. 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 inside module-info.java because hyphens are minus subtraction operators in Java syntax.
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 KeywordPurpose inside module-info.javaLegal as variable/method in standard .java?
moduleDeclares a named moduleYes (e.g., int module = 10;)
openDeclares an open moduleYes (e.g., public void open() {})
requiresDeclares a module dependencyYes (e.g., boolean requires = true;)
exportsExports a package to consumersYes (e.g., String exports = "data";)
opensOpens a package for reflectionYes (e.g., int opens = 0;)
usesDeclares a service interface consumptionYes (e.g., void uses(int count) {})
providesDeclares a service provider implementationYes (e.g., int provides = 42;)
withPairs with provides to specify provider classYes (e.g., record with() {})
toRestricts exports or opens to target modulesYes (e.g., String to = "user";)
transitiveModifier on requires for implied readabilityYes (e.g., boolean transitive = false;)
staticModifier on requires for compile-time optionalYes (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) or Method.setAccessible(true) on non-public types or unexported packages throws java.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 to javac during 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 module cannot contain individual opens directives inside its body. Attempting to write an opens statement inside an open module causes a compile-time error.

Module Encapsulation Comparison Matrix

Capability / PropertyStandard Named Module (module)Open Module (open module)
Compile-Time AccessExported packages onlyExported packages only
Runtime Standard AccessExported packages onlyExported 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?YesYes
Primary Use CaseSecure business logic, APIs, librariesEntity modules, domain models, Spring/Hibernate models
Loading diagram...
Modular JDK Dependency Graph Rooted at java.base
Test Your Knowledge

What is the result when a developer explicitly declares requires java.base; inside a module-info.java file?

A
B
C
D
Test Your Knowledge

Which statement accurately describes the behavior of contextual (restricted) keywords in Java SE 21 modular development?

A
B
C
D
Test Your Knowledge

Which statement correctly describes the characteristics and syntax constraints of an open module in JPMS?

A
B
C
D
Test Your Knowledge

Where must the module-info.java source file be placed within a module's directory structure?

A
B
C
D