Understanding Java 9's Module System

Introduction to Java 9 Modularity

Java 9 introduced a fundamental change to the platform's architecture with the Java Platform Module System (JPMS), developed under Project Jigsaw. This system addresses long-standing issues in Java development, particularly classspath complexity and the lack of strong encapsulation at the module level.

The Problem: Classpath Hell

Traditional Java applications suffer from "Jar Hell" - similar to Windows' DLL Hell - where managing dependencies becomes complex with numerous JAR files on the classpath. This leads to:

  • Long and unwieldy classpath strings
  • Difficulty in managing dependency versions
  • No clear way to enforce API boundaries
  • Bloated deployments for smaller devices

Getting Started: First Module

Prerequisites

Ensure you're running Java 9 or later:

java -version
java version "9"
Java(TM) SE Runtime Environment (build 9+181)
Java HotSpot(TM) 64-Bit Server VM (build 9+181, mixed mode)

Creating a Basic Module

Create a simple Java class:

// src/com/example/launcher/App.java
package com.example.launcher;

public class App {
    public static void main(String[] args) {
        System.out.println("Welcome to Java modules!");
    }
}

Create the module descriptor:

// src/module-info.java
module first.module {}

Compilation and Packaging

# Compile the module
javac -d target/classes src/module-info.java src/com/example/launcher/App.java

# Package as modular JAR
jar --create --file lib/first-module.jar --main-class com.example.launcher.App -C target/classes .

Running the Module

# Execute using module path
java --module-path lib -m first.module

Module System Fundamentals

Module Hierarchy

Java introduces a new layer in the package hierarchy:

module → package → class/interface

A module is:

  • A named collection of packages
  • A self-decsribing unit with explicit dependencies
  • A container that controls which packages are accessible externally

Creating an Exporting Module

// module-info.java
module com.example.api {
    exports com.example.api.core;
    exports com.example.api.utils;
}

Only exported packages are accessible to other modules. Non-exported packages remain strongly encapsulated.

Module Dependencies

// Consumer module
module com.example.app {
    requires com.example.api;
    requires java.sql;  // Standard module
}

// API module with its own dependency
module com.example.api {
    requires com.example.core;
    exports com.example.api.core;
}

Built-in Modules

The JDK itself is modularized. The java.base module contains fundamental classes:

module java.base {
    exports java.lang;
    exports java.io;
    exports java.util;
    // ... more core packages
}

All modules automatically require java.base, similar to how all code implicitly imports java.lang.*.

Reliable Configuration

The module system validates dependencies at compile and runtime:

  • Compile-time: Detects missing required modules
  • Runtime: Ensures all required modules are present
  • No implicit dependencies: All dependencies must be explicit

Strong Encapsulation

Modules enforce strict access control:

// Attempting to access non-exported package
module com.example.consumer {
    requires com.example.provider;
}

// If com.example.provider doesn't export com.example.provider.internal:
// Compile-time error: package com.example.provider.internal is not visible

Exploring Built-in Modules

List all available modules:

java --list-modules

Inspect a specific module's contents:

jmod list $JAVA_HOME/jmods/java.base.jmod

Practical Example: Multi-module Application

Create a Service Module

// service/src/com/example/service/GreetingService.java
package com.example.service;

public class GreetingService {
    public void greet(String name) {
        System.out.println("Hello, " + name + "!");
    }
}

// service/src/module-info.java
module com.example.service {
    exports com.example.service;
}

Create Consumer Module

// consumer/src/com/example/consumer/Main.java
package com.example.consumer;

import com.example.service.GreetingService;

public class Main {
    public static void main(String[] args) {
        new GreetingService().greet("modular world");
    }
}

// consumer/src/module-info.java
module com.example.consumer {
    requires com.example.service;
}

Build and Run

# Compile service module
javac -d service/target/classes service/src/module-info.java service/src/com/example/service/GreetingService.java
jar --create --file service/lib/service.jar -C service/target/classes .

# Compile consumer module
javac --module-path service/lib -d consumer/target/classes consumer/src/module-info.java consumer/src/com/example/consumer/Main.java
jar --create --file consumer/lib/consumer.jar --module-path service/lib --main-class com.example.consumer.Main -C consumer/target/classes .

# Run application
java --module-path service/lib:consumer/lib -m com.example.consumer

Module System Tools

jlink: Custom Runtime Images

Create optimized JRE containing only needed modules:

# Create minimal JRE with java.base only
jlink --module-path $JAVA_HOME/jmods --add-modules java.base --output custom-jre

# Size comparison
du -sh $JAVA_HOME custom-jre
# 493M    /path/to/jdk-9
# 35M     custom-jre

# Include application modules
jlink --module-path $JAVA_HOME/jmods:service/lib:consumer/lib \
      --add-modules com.example.consumer \
      --output app-runtime

# Create launcher
jlink --module-path $JAVA_HOME/jmods:service/lib:consumer/lib \
      --add-modules com.example.consumer \
      --launcher runapp=com.example.consumer \
      --output app-runtime

jdeps: Dependency Analysis

jdeps --module-path service/lib consumer/lib/consumer.jar

jmod: Module Manipulation

# Create jmod file
jmod create --class-path consumer/lib/consumer.jar consumer.jmod

# List contents
jmod list consumer.jmod

jdeprscan: Deprecated API Scanner

jdeprscan library.jar

Key Concepts Summary

  • Module Descriptor: module-info.java defines module structure
  • Modular JAR: Standard JAR containing module descriptor
  • Reliable Configuration: Explicit dependency validation
  • Strong Encapsulation: Export-based access control
  • Readable Modules: Established dependency relationships

Modules vs Traditional JARs

Aspect JAR Module
Structure Flat collection of classes Structured with explicit exports
Dependencies Implicit via classpath Explicit requires clauses
Encapsulation Package-level only Module-level control
Configuration Manual classpath management Automatic module resolution

Practical Applications

  1. Custom Runtimes: Deploy minimal JREs for containers/embedded systems
  2. Plugin Architectures: Anforce clean API boundaries between components
  3. Microservices: Optimize deployment size and startup time
  4. Library Development: Provide stable, versioned APIs with clear contracts

Important Considerations

  • Circular dependencies are prohibited between modules
  • Modern IDEs require updates for full module-path support
  • Modular JARs can still be used on classpath (as traditional JARs)
  • Migration to modules is incremental - not an all-or-nothing change

Tags: Java9 JPMS Modularity Project Jigsaw Java Modules

Posted on Thu, 23 Jul 2026 16:40:13 +0000 by wowiz