View All
View All
View All
View All
View All
View All
View All
View All
View All
View All
View All

How Java Modules Are Transforming Application Development!

By Rohan Vats

Updated on Jul 02, 2025 | 21 min read | 18.43K+ views

Share:

Did you know? Before Java 9, the JDK was a monolith. It was one massive package, and you couldn’t pick and choose the parts you needed. Java 9 introduced a module system to break this monolith into smaller, more manageable pieces.

Java Modules, introduced in Java 9, offer a powerful solution to the challenges of managing complex Java applications. They resolve "JAR Hell" by enforcing strict module boundaries and explicit dependencies, unlike the loose dependency model of traditional JARs. 

This fundamental shift enhances reliability by preventing accidental access to internal APIs, improves scalability through more efficient memory usage and faster startup times, and strengthens security by allowing for fine-grained control over what code is exposed. The result is more maintainable and predictable systems that are easier to build and deploy.

This blog explores the core concepts behind construction and runtime of Java Modules. It will guide you through the practical steps of building and launching modular Java projects.

Don't just learn Java Modules, truly excel at building robust, predictable software. Explore Online Software Development Courses from top universities to learn the most in-demand technologies and languages that leading companies use. 

Java Modules: Core Concepts of Java Module System 

The Java Module System, introduced in Java 9, enhances the Java ecosystem by enabling modularity within the platform. It complements existing features like the classpath and JVM by offering improved encapsulation, dependency management, and runtime optimization. 

Java modules allow applications to be more organized, maintainable, and scalable, while reducing memory usage and startup times. This system integrates seamlessly with Java’s principles of robustness and platform independence. It modernizes how complex systems are built and managed.

In 2025, professionals who can use advanced Java programming techniques to streamline business operations will be in high demand. If you're looking to develop skills in programming, here are some top-rated courses to help you get there:

Understanding Java Modules

At its core, a Java module is a named, self-describing collection of code (packages and classes) and data (resources). It explicitly declares its dependencies on other modules and defines which packages are accessible to other modules. This structure represents a significant evolution beyond traditional JARs, which often suffered from weak encapsulation and ambiguous dependencies.

This strong encapsulation and explicit dependency management are central to overcoming the classpath issues and promoting more reliable and maintainable applications. During runtime, the Java Virtual Machine (JVM) uses these declarations to construct a module graph, rigorously verifying all dependencies and failing fast if any are missing or unsatisfiable.

Also Read: Java Tutorial: Learn Java Programming From Scratch For Beginners

Java 9 Module System

The Java 9 Module System, or Project Jigsaw, was a monumental release that fundamentally re-architected the Java platform into interconnected modules. This overhaul brought strong encapsulation and explicit dependencies. It significantly improves security and performance by enabling leaner, more focused application runtimes.

It specifically aimed to dismantle long-standing issues like "JAR Hell" and circumvent the "classpath dilemma." This allows developers to build more reliable, scalable, and maintainable applications. Java Platform Module System offers a blueprint for robust software architecture by self-contained components and clearly defining their interactions.

Also Read: Top 32 Exception Handling Interview Questions and Answers in 2025 [For Freshers & Experienced]

Key Concepts of the Java Platform Module System (JPMS)

Let us now learn about the fundamental declarations and mechanisms that define a module's behavior and interactions within the Java ecosystem.

1. Module Declaration (module-info.java): The heart of any Java module is its module descriptor file, module-info.java. This file, located in the module's root directory, acts as its manifest, defining its name, readability, and accessibility rules.

// Example module-info.java
module com.example.mymodule {
    // Module declarations go here
}

Coverage of AWS, Microsoft Azure and GCP services

Certification8 Months

Job-Linked Program

Bootcamp36 Weeks

2. Module Name: Every module must have a unique name. This name is crucial for the module system to identify and resolve dependencies. Best practice suggests using a reverse-domain name convention (e.g., com.example.mymodule) to ensure uniqueness and avoid conflicts.

3. Requiring Dependencies (requires): Java 9 modules explicitly declare their dependencies on other modules using the required keyword within module-info.java. This makes the module system aware of the module graph, ensuring all necessary modules are present at runtime.

module com.example.mymodule {
    requires java.base; // Declares a dependency on the base Java module
    requires com.example.anothermodule; // Depends on a custom module
}

Also Read: 15 Essential Java Full Stack Developer Skills in 2025

4. Exporting and Opening Packages (exportsopens): All packages within a module are encapsulated by default. To make packages accessible to other modules, they must be explicitly exports or opens.

  • exports: Allows other modules to access the public types within the specified package at compile-time and runtime.
  • opens: Primarily used for reflective access at runtime. It allows other modules to use reflection (e.g., Class.forName()setAccessible(true)) to access all members (public, protected, private) within the specified package. Unlike exports, which support compile-time access, opens is for runtime reflection and does not expose types for compilation.
module com.example.mymodule {
    exports com.example.mymodule.api; // Public API for other modules
    opens com.example.mymodule.internal; // Allows reflection on internal package
}

5. Qualified Exports/Opens (exports ... toopens ... to): Sometimes, you don't want to export a package to all modules but only to specific ones. Qualified exports/opens allow you to specify a comma-separated list of target module names. This provides fine-grained control over visibility, preventing unintended dependencies on internal APIs. Use this when exposing internal APIs to trusted plugins without polluting the public module interface.

module com.example.core {
    exports com.example.core.internal.spi to com.example.plugin.loader; // Only plugin.loader can access
}

Also Read: Why is Java Platform Independent Language-A Detailed Guide

6. Using and Providing Services (usesprovides): The service mechanism in JPMS facilitates the creation of pluggable architectures where modules can use (discover) implementations of a service interface and provide (implement) a service.

  • uses: Declares that a module may use a service defined by a given interface or abstract class. The module system can then locate implementations of this service via java.util.ServiceLoader.
  • provides ... with: Declares that a module implements a specific service interface.
// Module com.example.serviceuser
module com.example.serviceuser {
    uses com.example.service.MyService; // Declares intent to use MyService
}

// Module com.example.serviceprovider
module com.example.serviceprovider {
    provides com.example.service.MyService with com.example.serviceprovider.MyServiceImpl; // Provides an implementation
}

Other Important Considerations:

1. Automatic Modules: When you place a traditional JAR file (without a module-info.java) on the module path, the Java Module System treats it as an "automatic module."

  • Its name is derived from the JAR file name (or Automatic-Module-Name in MANIFEST.MF).
  • It implicitly exports all its packages.
  • It implicitly reads all other modules (both named and other automatic modules) on the module path.
  • While convenient for migrating legacy JARs, relying heavily on automatic modules can weaken the benefits of strong encapsulation and explicit dependencies. 

They may also introduce split-package errors if the same package exists in multiple automatic modules.

2. Unnamed Module: Any classes loaded from the traditional classpath (not the module path) belong to the "unnamed module."

  • The unnamed module automatically exports all its packages.
  • It implicitly reads all other modules (named and automatic).
  • However, named modules cannot require the unnamed module. 

This mechanism primarily exists for backward compatibility, allowing legacy code to run on a modular JVM. This enforces modular integrity. Legacy code should be gradually migrated to named modules to participate fully in JPMS.

To build truly intelligent and data-backed applications, you also need to enhance your analytical toolkit.  Explore our Hypothesis Testing Crash Course – master A/B testing and make data-driven decisions. Elevate your entire skill set today!

Also Read: 50 Java Projects With Source Code in 2025: From Beginner to Advanced

With a solid understanding of Java 9 Modules, the next logical step is to see them in action. We'll now walk through Building and Launching Modular Java Programs.

Building and Launching Modular Java Programs

Building and Launching Modular Java Programs involves dividing your application into self-contained modules, each with specific functionality. By defining modules with a module-info.java descriptor, you ensure clear dependencies, exports, and access controls. 

During launch, the Java Module System optimizes performance by loading only necessary modules, improving security and reducing application size. This modular approach enhances development, deployment, and overall efficiency, making the program more maintainable and scalable.

How to Create a Module in Java? Compilation of Java Module

To create a module in Java, start by defining a module-info.java file. Here, you declare the module’s name, list its dependencies with requires, and specify accessible packages using exports. Organize your classes and packages within the module directory for proper encapsulation. 

Compile the module using the javac command with the --module-path flag to include dependencies, and run it with the java command. Specify the --module-path and --module options. This modular structure enhances code organization, maintainability, and scalability.

Step 1: Project Structure

Organize your source code into a directory structure that reflects your module. Each module should have its root directory, and inside that, typically a src folder containing your module-info.java and package directories.

my-modular-project/
├── my.module.name/
│ ├── src/
│ │ ├── module-info.java
│ │ └── com/
│ │ └── example/
│ │ └── MyClass.java
└── out/

Step 2: Create module-info.java

Inside your module's src directory, create the module-info.java file. This is where you declare your module's name, its dependencies, and which packages it exports.

// my.module.name/src/module-info.java
module my.module.name {
    // We export com.example so other modules can use its public classes
    exports com.example;
    // If MyClass needed types from java.base (like String, System), we'd require it
    // requires java.base; //
}

Explanation:

  • module my. module.name { ... }: Defines a module named my.module.name.
  • exports com.example;: Specifies that the com.example package within this module is accessible to other modules that require my.module.name. Any classes not in an exported package remain strongly encapsulated.

Step 3: Write Your Java Code

Create your regular Java source files within their respective packages.

// my.module.name/src/com/example/MyClass.java
package com.example;

public class MyClass {
    public String getGreeting() {
        return "Hello from MyClass in a module!";
    }
}

Step 4: Compile the Module

Use the javac command. The key difference from traditional compilation is the use of the --module-source-path option to tell javac where to find your module source files, and --module to specify the module you are compiling. This creates compiled class files in a designated output directory.

# Navigate to the project root: my-modular-project/
mkdir -p out/my.module.name # Create output directory for the module

javac --module-source-path my.module.name/src \
      -d out \
      my.module.name/src/module-info.java \
      my.module.name/src/com/example/MyClass.java

Explanation:

  • --module-source-path my.module.name/src: Tells javac to look for module source roots in my.module.name/src.
  • -d out: Specifies out as the destination directory for compiled classes. javac will automatically create the my.module.name subdirectory inside out for the compiled module.

Note: javac places the output in a directory matching the module name under ‘out’ automatically.

  • my.module.name/src/module-info.java my.module.name/src/com/example/MyClass.java: The specific source files to compile. If compiling multiple modules, each should follow the directory structure rootdir/module.name/src.

After compilation, your out directory will contain the compiled module:

out/ 
├── my.module.name/
│ ├── module-info.class
│ └── com/
│ └── example/
│ └── MyClass.class

You can get a better hang of Java with upGrad’s free Core Java Basics course. It covers variables, data types, loops, and OOP principles to build strong coding skills. Perfect for aspiring developers, students, and professionals transitioning to Java.

Also Read: Java Language History: Key Milestones and Development

To truly understand the power of the Java Module System, we'll now explore the prominent Java Module Construction and Runtime concepts.

Top 5 Advanced Concepts for Java Module Construction and Runtime

Advanced concepts in Java module construction and runtime focus on optimizing modularity, scalability, and performance. Key concepts include managing module dependencies for flexibility, using access control mechanisms like exports and opens for secure encapsulation. It includes configuring a custom module path for efficient module organization. 

The jlink tool enables the creation of streamlined modular runtime images, reducing application size. Additionally, dynamic module resolution supports flexible module interactions, crucial for building scalable, secure, and efficient Java applications.

1. Modular JARs

Modular JARs are essentially regular .jar files with a twist: they contain a compiled module-info.class file at their root. This file, derived from your module-info.java source, acts as a module descriptor, providing the Java runtime with all the necessary information about the module – its name, dependencies, and exported packages. Crucially, the module name defined in module-info.class must match the actual module name used for resolution on the module path.

When the JVM encounters a JAR with this descriptor on the module path, it treats it as a well-defined module, adhering to strong encapsulation and explicit dependency rules. This packaging ensures that modular applications are compact and efficient, as only the necessary components are included.

2. Module Path vs. Classpath

The distinction between the module path and the classpath is fundamental in modular Java.

  • Classpath: This is the legacy mechanism Java has used for decades to locate classes and resources. It's a flat list of JARs and directories. The classpath offers weak encapsulation; all JARs on it are effectively "friends," and name collisions (like "JAR Hell") are common. Code on the classpath operates within the unnamed module, which implicitly exports all its packages and reads all other modules (named or automatic). This leniency often leads to runtime issues due to missing or conflicting dependencies.
  • Module Path: Introduced with Java 9, the module path is specifically designed for the Java Module System. When you place a JAR on the module path, it's either a fully defined named module (with a module-info.class) or an automatic module (a regular JAR treated as a module). The module path enforces strict rules:
    • Strong Encapsulation: Only explicitly exported packages are visible to other modules.
    • Explicit Dependencies: Modules must declare what they requires.
    • No Split Packages: A package cannot exist in multiple modules.
    • Module Identity: Each module has a unique name.

The module path ensures that the JPMS can build a reliable and predictable module graph at startup, significantly reducing runtime errors caused by classpath issues.

Feature Classpath (Legacy) Module Path (JPMS)
Encapsulation Weak (all public types are visible) Strong (only exports packages are visible)
Dependencies Implicit (relies on order, "JAR Hell") Explicit (requires declarations)
Package Rules Split packages allowed (same package in multiple JARs) No split packages allowed (single module owns the package)
Resolution Flat search, first-come, first-served Graph-based, strict resolution
Runtime Issues Common (e.g., NoClassDefFoundError) Minimized by early detection
Default Module Unnamed Module Named Modules, Automatic Modules

 

3. Module Resolution and Module Graph

When a modular Java application starts, the Java Virtual Machine doesn't just blindly load JARs. Instead, it engages in a sophisticated process called module resolution. This involves:

  1. Scanning the Module Path: The JVM first examines all modules specified on the module path.
  2. Reading Module Descriptors: For each module found, it reads its module-info.class file to understand its name, its explicit requires dependencies, and its exports.
  3. Building the Module Graph: The JPMS then constructs a directed graph where each node is a module and directed edges represent requires relationships. This graph is built by iteratively adding all directly and transitively required modules until the set of observable modules is complete.
  4. Verification: During this construction, the JPMS performs rigorous checks:
    • Uniqueness: Ensures no two modules have the same name.
    • No Cycles: Detects circular dependencies, though cycles are generally allowed in requires relationships.
    • No Split Packages: Verifies that a package is not defined by more than one module in the graph.
    • Completeness: Confirms that all required modules are actually present on the module path.

If any of these checks fail, the application will not start, providing early detection of configuration issues that would have previously led to mysterious runtime errors. This robust resolution process is a cornerstone of JPMS's reliability.

4. The Base Module (java.base)

The java.base module is the bedrock of the Java Platform Module System. It's the fundamental module that every other module implicitly requires, even if you don't explicitly add requires java.base; in your module-info.java file (though it's good practice for clarity). This module contains the most basic and essential functionalities of the Java SE Platform, including:

  • Core Java Language APIs: Packages like java.lang (e.g., ObjectStringSystemThread), java.util (e.g., collections, dates), and java.io (e.g., input/output streams).
  • Fundamental Concurrent Utilities: Basic concurrency primitives.
  • Reflection APIs: Core reflection capabilities.
  • Other essential utilities: Foundational classes needed by virtually any Java application.

Its implicit requirement ensures that all modules access these core building blocks without redundant declarations, making it the unavoidable root of every module graph.

5. Runtime Images (jlink)

Prior to Java 9, deploying a Java application meant bundling your code with a full JDK or JRE installation, which could be quite large. The jlink tool, introduced with JPMS, changes this. It analyzes your application's module graph (including your application modules and the JDK modules it depends on). 

Then, it creates a custom runtime image that includes only the required JVM parts (like Java.baseJava.desktop, etc.). jlink can consume modules packaged as modular JARs or the JDK's own .jmod files.

Benefits of jlink:

  • Reduced Size: Significantly smaller deployment packages include only essential modules, which is particularly beneficial for cloud deployments, microservices, and containerized environments.
  • Improved Security: By excluding unused modules, you reduce the attack surface. If a vulnerability is found in a module your application doesn't use, it won't be in your custom runtime.
  • Faster Startup: A smaller runtime means fewer classes to load, potentially leading to faster application startup times.
  • Self-Contained Applications: The resulting runtime image is self-contained, meaning you don't need a pre-installed JDK/JRE on the target machine. This simplifies deployment.

How jlink works (High-Level)?

You typically use the jlink command-line tool, specifying your application's modules and the desired output directory. It then analyzes your module graph and constructs the runtime.

# Example jlink command
jlink --module-path <path-to-your-modules>:<path-to-jdk-modules> \
      --add-modules <your-main-module>,<other-needed-modules> \
      --output my-modular-app-runtime

This effectively "links" your application with a minimal, optimized JVM, making deployment much more efficient and secure. This is a game-changer for many modern Java deployments.

Also Read: Types of Variables in Java: Java Variables Explained

Having explored the advanced technicalities of Java Modules, including packaging, runtime behavior, and image creation, it's natural to consider their real-world impact. 

The Dual Side of Modularity: Benefits and Challenges of Java Modules

Although modularity improves maintainability and scalability, it can create integration challenges. For instance, modularizing an e-commerce platform allows independent updates to components, boosting performance by loading only essential modules. 

However, integrating legacy systems can be tricky, as their non-modular design complicates dependency management and module descriptor maintenance. It requires careful planning for smooth integration.

Here are the common benefits of Java modules:

Benefits

Details

Encapsulation Modules control which packages are exposed, reducing the risk of other parts of the codebase accessing internal logic by mistake.
Dependency Clarity Each module declares exactly what it needs and what it offers, making dependencies easier to track and maintain.
Improved Security By exposing only required APIs, modules reduce the chances of unauthorized access to sensitive parts of your code.
Performance Boost The module system can eliminate unused code at runtime, leading to faster execution and reduced memory usage.
Organized Codebase Applications can be split into logical modules, improving structure, readability, and maintainability.

Next let’s skim though the limitations of Java modules. 

Limitations

Details

Learning Overhead Developers new to Java modules may find it tricky to understand syntax, rules, and best practices.
Legacy Integration Older libraries not designed with modules in mind might need adjustments or rewrites to fit in.
Scaling Complexity Large projects can become harder to manage due to additional planning required for module boundaries.
Tool Compatibility Some build tools, plugins, or IDEs may have incomplete or limited support for the module system.
Build Performance Modular builds can take longer, especially in large applications, due to extra resolution and validation steps.

Also Read: Python vs Java: Mastering the Best Language For Your Career

Next, let’s look at how upGrad can help you improve your understanding of Java programming.

Expand Your Java Expertise: A Journey with upGrad

To expand your Java expertise and leverage modularity effectively, focus on hands-on practice with JPMS tooling like jlink and dive into real-world modular project structures. Continuously experimenting with module declarations and understanding error messages will solidify your grasp on this powerful paradigm.

However, understanding modern Java development can be overwhelming. Without structured guidance, grasping advanced concepts like Java Modules often feels like an uphill battle. upGrad bridges this gap, providing industry-aligned tech courses to equip you with in-demand skills.

 Here are some additional courses that can further enhance your development skills:

Still struggling to find the right Java software development course to match your goals in 2025? Reach out to upGrad for personalized counseling and valuable insights, or visit your nearest upGrad offline center for more details.

Boost your career with our popular Software Engineering courses, offering hands-on training and expert guidance to turn you into a skilled software developer.

Master in-demand Software Development skills like coding, system design, DevOps, and agile methodologies to excel in today’s competitive tech industry

Stay informed with our widely-read Software Development articles, covering everything from coding techniques to the latest advancements in software engineering.

Reference:
https://medium.com/@pvprasanth474/whats-new-in-java-9-the-top-4-features-you-should-know-cd2578ca1ed1

Frequently Asked Questions (FAQs)

1. How does the module system specifically prevent NoClassDefFoundError at runtime, which was familiar with the classpath?

2. What happens if I have a package with the same name in two different JARs and try to use them on the module path?

3. Can a module expose some packages only to specific other modules, rather than to everyone?

4. What is the main benefit of jlink over just bundling a full JDK/JRE with my application?

5. How does the requires transitive keyword simplify module declarations for consuming modules?

6. What's the purpose of opens versus exports in a module-info.java file?

7. Can a named module read a package from the traditional classpath?

8. What is the significance of java.base being implicitly required by all other modules, and how does it relate to preventing NoClassDefFoundError for core classes?

9. How can the module system help prevent supply chain attacks or accidental inclusion of vulnerable code?

10. What are automatic modules, and why are they considered a temporary solution during migration?

11. How does JPMS enhance the security of an application at runtime?

Rohan Vats

408 articles published

Software Engineering Manager @ upGrad. Passionate about building large scale web apps with delightful experiences. In pursuit of transforming engineers into leaders.

Get Free Consultation

+91

By submitting, I accept the T&C and
Privacy Policy

India’s #1 Tech University

Executive PG Certification in AI-Powered Full Stack Development

77%

seats filled

View Program

Top Resources

Recommended Programs

upGrad

AWS | upGrad KnowledgeHut

AWS Certified Solutions Architect - Associate Training (SAA-C03)

69 Cloud Lab Simulations

Certification

32-Hr Training by Dustin Brimberry

upGrad KnowledgeHut

upGrad KnowledgeHut

Angular Training

Hone Skills with Live Projects

Certification

13+ Hrs Instructor-Led Sessions

upGrad

upGrad

AI-Driven Full-Stack Development

Job-Linked Program

Bootcamp

36 Weeks