# Modules and Modular Applications

> Chapter 17 of the Courseiva ORACLE-OCP-JAVA17 curriculum — https://courseiva.com/learn/oracle-ocp-java17/modules-and-modular-applications

**Official objective:** 12.1 — Describe the modular JDK, create and export modules, use module declarations, and understand module dependencies.

## Introduction

How do you stop one part of a Java application accidentally breaking another part? In older versions of Java, every class could see every other class, which led to fragile code and confusing errors. Module declarations solve this by letting you explicitly decide which packages to share and which to hide, and the 1Z0-829 exam tests whether you can create and use these declarations correctly.

## The Shared-Flat Renovation Analogy

A big old house that a group of friends is converting into shared flats. Each friend gets a private room, but they have to share the kitchen, bathroom, and living room. At first, everyone just piles their stuff anywhere. The kitchen becomes a mess of overlapping gadgets — someone’s toaster blocks the blender, and you can never find the good knives because they’re buried under someone else’s pans. When a new friend moves in, you have to open every cupboard to work out what’s already there, and sometimes you accidentally start using a pan that someone else is still using. It’s chaos. You waste time, break things, and nobody knows what’s public and what’s private.

Now imagine you renovate the house with clear rules. Each friend gets a lockable room with a label — “Alex’s room: contains a desk, a lamp, and a kettle (if you need the kettle, ask Alex).” The shared kitchen is reorganised: one cupboard for cups, one for spices, and a “public only” zone for things like the kettle and the microwave. Anyone moving in gets a welcome pack that says, “These are the shared things you can use. These are not.” Suddenly, nobody accidentally borrows someone else’s personal pan. Moving in is fast because you don’t have to inspect every box. And if a friend leaves, you only have to clean out their labelled room — not the whole house. That’s module declarations: you label what parts of your Java code are “public” (shared) and what are “private” (hidden), so other pieces of code don’t accidentally trip over each other.

## Core explanation

In Java 9, Oracle introduced the module system — a way to organise your code into separate, self-contained units called modules. A module is like a labelled box for related Java packages and resources. Before modules, Java had packages, but they couldn’t enforce privacy. Any class in any package could use reflection (a way to inspect code at runtime) to access private members of classes in other packages. That made it hard to refactor libraries without breaking downstream code.

Every module has a key file: module-info.java. This file sits at the root of the module and declares what the module does. It uses two main keywords: exports and requires. The exports keyword says, “I am deliberately opening these packages for other modules to use.” The requires keyword says, “I depend on another module to work at all.”

For example, consider a simple library that provides utility date functions. Your module-info.java might look like this:

module com.example.dates {
    exports com.example.dates.api;
    requires com.example.timezone;
}

This means only the com.example.dates.api package is visible outside the module. Everything else in the module — any private helper classes in com.example.dates.internal — is completely hidden. Other modules cannot see those internal classes, even with reflection. The requires line says: “Before this module can be used, the module com.example.timezone must be present and available.” If it’s missing, the Java virtual machine (JVM — the program that runs Java bytecode) will throw an error at startup, not later when you run the code.

The module system also guards a dangerous feature called reflection. Before modules, you could use reflection to access private fields and methods of any class from anywhere. This was a security and maintenance nightmare. Now, unless a module explicitly opens a package for reflection (using the opens keyword), reflection fails on its classes. That makes code more secure and easier to evolve.

What problem does this solve? Let’s say you write a Java application that uses a library called “libA” version 1.0, and another library called “libB” that also uses “libA” but version 2.0. In the old Java, both versions of “libA” would be loaded into the same classpath (the list of locations Java searches for class files), and the last one loaded would win — meaning both libraries might see the wrong version and crash. With modules, each module can declare exactly which version it requires, and the JVM ensures only one version runs, or it fails early with a clear error message.

Modules also improve startup performance because the JVM knows exactly which classes to load. It can skip scanning the entire classpath. For tiny applications this doesn’t matter, but for large systems it can save seconds.

Finally, modules are the foundation of the modular JDK (Java Development Kit — the software you use to compile and run Java). Oracle split the old monolithic rt.jar into hundreds of modules. You can now list them with the command:

java --list-modules

This shows all modules available in your JDK, such as java.base (always required), java.sql, java.xml, and many more. If you build a simple command-line app that doesn’t need SQL or XML, you can use a tool called jlink to create a custom runtime image containing only the modules you actually use. That reduces your application’s size dramatically — from 300+ MB down to maybe 30 MB.

To summarise, the module system gives you three main benefits:
- Strong encapsulation: you control exactly what is visible to other modules.
- Explicit dependencies: you state which modules your code needs, and the JVM checks them at startup.
- Scalable runtime: you can strip the JDK down to only the parts your application uses.

## Real-world context

Consider a financial services company that builds a risk-assessment system using Java. The system has three main sections: a data-ingestion module that reads market data from APIs, a calculation engine that computes risk metrics, and a reporting module that generates PDF reports. In a legacy (pre-module) setup, every class was on the same classpath. A developer in the reporting team could accidentally import and use a class from the data-ingestion module that was meant to be internal, like a buffer-handler that didn’t clean sensitive data properly. This could lead to a compliance breach.

With modules, the team redesigns the application:

- module com.bank.ingestion {
    exports com.bank.ingestion.api;
    exports com.bank.ingestion.dto;  // data transfer objects shared with calculation
    requires com.bank.calculation;
}

- module com.bank.calculation {
    exports com.bank.calculation.api;
    requires transitive com.bank.ingestion;  // anyone who uses calculation also gets ingestion
    requires java.math;
}

- module com.bank.reporting {
    requires com.bank.calculation;
}

Because the ingestion module uses exports only on its api and dto packages, the internal package com.bank.ingestion.internal.buffer is completely hidden. The reporting module cannot accidentally touch it, even with reflection, because the module does not open that package. The requires clauses ensure that when the application starts, Java verifies that all three modules exist and are compatible. If someone accidentally deletes the calculation module, the whole application fails fast with a clear error: "Module com.bank.calculation not found."

The company also uses jlink to produce a custom runtime image that omits JDK modules they never use, such as java.desktop and jdk.unsupported. This shrinks the deployment from 250 MB to 45 MB, which is crucial because they deploy the system on containerised servers where disk and memory are metered.

What does the IT professional actually do? 

- They start by analysing the existing codebase to identify internal packages that should never be exposed, then move them to separate packages inside the module.
- They create a module-info.java for each module, carefully listing exports and requires.
- They run jdeps (a Java tool to analyse dependencies) on their code to check for hidden dependencies on JDK internal APIs that would break the module migration.
- They use the --add-exports and --add-opens flags during migration as a temporary bridge to compile and run code that hasn’t been fully modularised yet. These flags are common in the exam as traps — they are not the intended long-term solution.
- They update their build system (Maven or Gradle) to place module-info.java in the correct directory: src/main/java/module-info.java for modules that are compiled into the main source set.

In short, the professional uses modules as a design tool to enforce boundaries and as a deployment tool to shrink the runtime. Both are tested on the 1Z0-829 exam.

## Exam focus

The 1Z0-829 exam tests module knowledge in several specific ways. First, you must know the exact syntax of module-info.java. The exam expects you to recognise valid keywords: module, exports, requires, requires transitive, opens, provides … with, and uses. You must know that a module declaration is a Java file that ends with .java and must reside in the root of the compiled module directory.

Common trap patterns include:

- The exam shows a module-info.java that exports a package that doesn’t exist, or requires a module that doesn’t exist. The correct answer is “this module will not compile.” You must spot the mismatch.
- The exam asks about the java.base module. It is always implicitly required — you never need to write requires java.base; but it’s not an error to do so. However, you can’t remove its dependency.
- The exam tests requires transitive. If module A requires transitive B, then any module that requires A also automatically gets access to B. This is a common point of confusion: the exam gives a scenario where module C requires A and wants to use B, but A used requires transitive B — then it’s legal. If A used just requires B, module C has no direct access to B and must add its own requires B.
- The exam tests open modules and open packages. An open module (declared as open module mymodule {}) allows full reflection on all its packages without individual opens statements. A single open package (opens package to module) only allows reflection for that package.
- The exam tests naming conventions: module names are typically reverse-domain-style (com.example.myapp), just like packages, but not identical — the module name and package names are independent.
- The exam tests jlink. You need to know that jlink takes a set of modules and produces a custom runtime image. The key exam point: jlink does not include all modules; you specify which root modules to include, and it resolves transitive dependencies.
- The exam tests the difference between a named module (one with module-info.java) and an unnamed module (code on the classpath without module-info.java). An unnamed module can access all exported packages from named modules, but named modules cannot access the unnamed module.
- The exam tests automatic modules. If you put a plain JAR file (without module-info.java) on the module path, Java treats it as an automatic module. The exam asks: what name does it get? Answer: the module name is derived from the JAR filename (after stripping version numbers and .jar).
- The exam tests module path vs classpath. Code on the module path respects module declarations; code on the classpath is treated as unnamed and does not respect module boundaries.
- The exam loves ‘split packages’ — two modules exporting the same package. This is illegal and causes a boot layer error. The exam will present a scenario and ask you to identify the error.

What exactly should you memorise for the exam?

- The keyword set: module, exports, requires, transitive, opens, provides, uses, with, to.
- The rule that exports without to makes the package accessible to all other modules; exports … to moduleA, moduleB limits visibility.
- The rule that requires transitive is used to make a dependency visible to downstream modules — it is not the same as requiring the module for internal use.
- The rule that you cannot access types from an exported package’s subpackages unless those subpackages are also exported. A single export does not export subpackages.
- The rule that the module-info.java file must be in the root source directory, not inside a package.

The exam questions are usually scenario-based: “Which statement is true if you compile and run this code?” or “What is the effect of this module declaration?”. Practice reading module declarations quickly and spotting missing or extra keywords.

## Step by step

1. **Analyse your application structure** — Identify logically separate units of code, such as a data-access layer, a business-logic layer, and a presentation layer. For each unit, determine which packages should be visible to other units and which should be internal. This step is crucial because it determines your module boundaries.
2. **Create a module-info.java file for each unit** — Place this file in the root source directory of the module. Declare the module name using reverse-domain convention (e.g., com.example.myapp.data). Use exports to list the packages you want to share. Use requires to list every other module your code directly depends on. Without this file, your code is not a named module.
3. **Decide whether you need requires transitive** — If your module re-exports a dependency’s types publicly (e.g., a method returns a type from another module), use requires transitive instead of requires. This ensures that any module depending on yours automatically gets access to that transitive module. The exam tests this distinction heavily.
4. **Compile the modules** — Use the javac command with the --module-source-path option to compile multiple modules together. Or use a build tool like Maven with the maven-compiler-plugin. The compiler enforces that every requires has a matching module and every exported package exists. Compilation fails with clear error messages if you violate module rules.
5. **Run the application with the module path** — Use the java command with the --module-path option pointing to directories or JAR files containing the compiled modules. Then use the -m option to specify the main module and class (e.g., -m com.example.myapp/com.example.myapp.Main). The JVM validates all module dependencies at startup and refuses to start if any are missing.
6. **Optionally create a custom runtime with jlink** — Run the jlink tool, providing it with your main module. jlink analyses transitive dependencies and creates a minimal Java runtime containing only the modules your application needs. This reduces the deployment size and startup time. The output is a directory that can be zipped and shipped.

## Comparisons

### exports vs opens

**exports:**
- Makes a package’s public types accessible for compile-time and runtime code use.
- Does not allow reflective access to non-public members.
- Syntax: exports com.example.pkg;

**opens:**
- Makes a package’s types accessible for reflective access (e.g., for frameworks).
- Allows reflective access to private fields and methods.
- Syntax: opens com.example.pkg;

### requires vs requires transitive

**requires:**
- Declares a dependency on another module for internal use only.
- Downstream modules do not automatically get access to that module.
- If module A requires B, and module C requires A, C cannot see B unless C also requires B.

**requires transitive:**
- Declares a dependency that is inherited by downstream modules.
- If module A requires transitive B, any module that requires A also automatically has access to B.
- Used when a module’s public API exposes types from the required module.

### Named module vs Unnamed module

**Named module:**
- Has a module-info.java file with explicit exports/requires.
- Resides on the module path.
- Cannot access classes from the unnamed module.

**Unnamed module:**
- Code placed on the classpath without a module-info.java.
- Can access all exported packages from named modules.
- Is completely invisible to named modules.

### jlink vs java --list-modules

**jlink:**
- Creates a custom runtime image containing only specified modules.
- Requires you to specify root modules; it resolves transitive dependencies.
- Output is a directory with a mini JDK that can be deployed.

**java --list-modules:**
- Lists all modules available in your current JDK installation.
- Does not create any output; it only displays information.
- Useful for discovering which modules you can require in your own code.

## Diagram

_A visual representation of three modules showing exports (green), requires (blue), requires transitive (red), and how the UI module accesses both the app module and the java.sql module via transitive dependency._

```mermaid
flowchart TD
    M[Module: com.example.myapp] -->|exports| P1[com.example.myapp.api]
    M -->|exports| P2[com.example.myapp.dto]
    M -->|requires| L[Module: com.example.lib]
    L -->|exports| PL[com.example.lib.services]
    M -->|requires transitive| T[Module: java.sql]
    T -->|exports| TS[java.sql package]
    U[Module: com.example.ui] -->|requires| M
    U -->|uses| P1
    U -->|uses| TS
    style M fill:#c0f0c0,stroke:#333,stroke-width:2px
    style L fill:#e0e0ff,stroke:#333,stroke-width:2px
    style T fill:#ffe0e0,stroke:#333,stroke-width:2px
    style U fill:#ffffc0,stroke:#333,stroke-width:2px
```

## Common misconceptions

- **Misconception:** Exports means I can use any class in that package, including private ones. **Reality:** Exports only means the package is accessible for use. Classes, interfaces, and members must still be declared public to be accessed outside the exporting module. Private members remain private even if the package is exported. (People confuse the visibility of the package with the visibility of types within it. Exports is about package visibility; Java access modifiers (public, private, etc.) control type visibility.)
- **Misconception:** The requires keyword makes all packages of the required module visible. **Reality:** requires only makes sure the required module is available at compile time and runtime. It does not automatically make all its packages visible — you still need exports on the required module to access its public types. (The phrasing 'requires' sounds like you gain access, but in module context it only means 'I depend on it'. Visibility is a separate concern handled by exports.)
- **Misconception:** I must always write requires java.base in my module-info.java. **Reality:** java.base is automatically resolved and does not need an explicit requires. Writing requires java.base; is legal but redundant, and the exam tests whether you know it’s optional. (Beginners often think all dependencies must be declared because java.base is so fundamental. The special treatment of java.base is a small rule that is easy to forget.)
- **Misconception:** If I export a package, all its subpackages are also exported. **Reality:** Exports are not recursive. If I export com.example.utils, the package com.example.utils.helpers is not automatically exported. Each subpackage must be exported separately. (This mirrors a common misunderstanding about Java packages themselves — subpackages are not logical children of parent packages. The module system enforces this explicitly.)
- **Misconception:** An automatic module is a module that runs without any module-info.java, and it is the same as an unnamed module. **Reality:** An automatic module is a JAR placed on the module path without its own module-info.java; it exports all its packages. An unnamed module is code on the classpath. They behave differently: automatic modules are visible to named modules; unnamed modules are not. (The terms 'automatic' and 'unnamed' sound similar. The exam banks on this confusion. The key difference is the placement: module path vs classpath.)

## Key takeaways

- Every Java module must have a module-info.java file at its root that declares its name, exports, requires, and optionally opens packages.
- The exports keyword makes a package visible to other modules, but only its public types are accessible.
- The requires keyword declares a dependency on another module; without it, that module’s exported packages are not visible.
- The java.base module is always implicitly required; you never have to write requires java.base;.
- Packages must be exported individually — exporting a parent package does not export its subpackages.
- An open module (declared with open module) grants full reflective access to all its packages without needing individual opens statements.
- jlink creates a custom runtime image containing only the modules you specify and their transitive dependencies.
- Split packages — the same package declared in two different named modules — are illegal and cause a boot layer error.

## FAQ

**Do I need to create a module-info.java file for every project?**

No. You can still run code on the classpath without modules. But to use the module system — and to pass the 1Z0-829 exam — you need to understand how to create and declare named modules with module-info.java.

**What happens if I require a module that doesn’t exist?**

The Java compiler (javac) or Java launcher (java) will throw an error: “module not found”. The application will not compile or run. This is exactly why modules are valuable: they fail early with clear messages.

**Can I see which modules are in my JDK?**

Yes. Run the command java --list-modules in your terminal. It shows all the JDK’s named modules such as java.base, java.sql, and java.xml. You can then require any of them in your own module-info.java.

**What’s the difference between exports and opens?**

exports makes the package’s public types available for normal code access. opens makes the package’s types available for reflective access (e.g., via libraries that use reflection like Spring or Hibernate). Many frameworks require opens because they need to inspect private fields.

**Can two modules have the same package name?**

No. This is called a “split package” and it is illegal for named modules. If two modules on the module path both export com.example.utils, the JVM will throw a LayerInstantiationException at startup.

**What is an automatic module?**

An automatic module is a regular JAR file (without module-info.java) that is placed on the module path. Java treats it as a module that exports all its packages. Its module name is derived from the JAR filename. This is a migration aid for libraries that haven’t been modularised yet.

## Check your understanding

1. **What is the name of the file that declares a Java module?**

   Answer: The file is called module-info.java and must be placed at the root of the module’s source directory.

2. **If module A requires module B, does module A automatically have access to all of module B’s packages?**

   Answer: No. Module A only has access to the packages that module B explicitly exports. requires alone does not grant package visibility.

3. **What is the difference between exports and opens in a module declaration?**

   Answer: exports allows normal compile-time and runtime access to public types; opens allows reflective access (including to private members) for frameworks.

4. **True or false: you must always declare requires java.base in your module-info.java. Answer and explain briefly.**

   Answer: False. The java.base module is implicitly resolved by the JVM and does not need an explicit requires declaration, although writing it is not an error.

5. **What happens if you put the same package in two different named modules on the module path?**

   Answer: The JVM throws a LayerInstantiationException because split packages are illegal for named modules.

---

Interactive version with quiz and diagrams: https://courseiva.com/learn/oracle-ocp-java17/modules-and-modular-applications
