Java tutorials > Modern Java Features > Java 8 and Later > What are modules in Java 9 (Project Jigsaw)?

What are modules in Java 9 (Project Jigsaw)?

Java 9 introduced modules as part of Project Jigsaw. Modules are a way to encapsulate and organize Java code, enhancing security, maintainability, and performance. Prior to Java 9, Java applications suffered from the problem of tightly coupled components and a lack of strong encapsulation. Every public class was visible to every other class on the classpath. Modules address this by providing a strong mechanism for defining dependencies and controlling access to internal APIs.

Core Concepts Behind Modules

Modules introduce several key concepts:

  1. Module Declaration (module-info.java): Each module has a module-info.java file that declares its name, dependencies (requires), and which packages it exposes to other modules (exports).
  2. Encapsulation: Modules provide strong encapsulation. Packages within a module are only accessible to other modules if explicitly exported. This prevents unintended access to internal implementation details.
  3. Dependencies: Modules declare their dependencies on other modules using the requires keyword. This allows the JVM to ensure that all required modules are present at runtime.
  4. Reliable Configuration: The module system ensures that all required modules are present and compatible at runtime, preventing common classpath-related errors.
  5. Reduced Runtime Footprint: By specifying explicit dependencies, the JVM can optimize the runtime environment and reduce memory consumption by only loading the necessary modules.

Module Declaration Example (module-info.java)

This module-info.java file defines a module named com.example.mymodule. It declares a dependency on the java.sql module (part of the JDK). It also exports the com.example.mymodule.api package, making its public classes accessible to other modules. The line `exports com.example.mymodule.utils to com.example.anothermodule;` demonstrates qualified exports, where the package is only visible to the specified module.

The `opens` keyword allows runtime reflection on the `com.example.mymodule.config` package. This is often used by frameworks like Spring or Hibernate.

module com.example.mymodule {
    requires java.sql;  // Depends on the java.sql module

    exports com.example.mymodule.api; // Exposes the API package
    exports com.example.mymodule.utils to com.example.anothermodule; // exports to a specific module
    opens com.example.mymodule.config; // opens package for reflection
}

Creating a Simple Module

This code demonstrates a simple module. The MyService class is part of the com.example.mymodule.api package. The module-info.java file exports this package, making the MyService class accessible to other modules.

// src/com.example.mymodule/com/example/mymodule/api/MyService.java
package com.example.mymodule.api;

public class MyService {
    public String getMessage() {
        return "Hello from MyModule!";
    }
}

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

Using a Module

This code shows how to use the com.example.mymodule module from another module, com.example.anothermodule. The module-info.java file for com.example.anothermodule declares that it requires com.example.mymodule. This allows the Main class to import and use the MyService class.

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

import com.example.mymodule.api.MyService;

public class Main {
    public static void main(String[] args) {
        MyService service = new MyService();
        System.out.println(service.getMessage());
    }
}

// src/com.example.anothermodule/module-info.java
module com.example.anothermodule {
    requires com.example.mymodule;
}

Real-Life Use Case

Consider a large enterprise application with many internal libraries. Using modules allows you to:

  • Encapsulate internal APIs: Prevent external modules from accessing and depending on internal implementation details, reducing the risk of unintended breaking changes.
  • Improve maintainability: Clearly define dependencies between modules, making it easier to understand and maintain the codebase.
  • Enhance security: Limit access to sensitive APIs by only exporting the necessary packages.
  • Optimize deployment: Create custom runtime images containing only the required modules, reducing the application's size and startup time.

Best Practices

  • Start with a Modular Design: When starting a new project, consider designing it with modules from the beginning.
  • Gradually Modularize Existing Code: If you have an existing codebase, modularize it incrementally. Start by creating module declarations and gradually refactor the code.
  • Keep Modules Small and Focused: Smaller modules are generally easier to understand, maintain, and reuse.
  • Use Qualified Exports Sparingly: Use qualified exports only when necessary to restrict access to specific modules. Avoid overusing them, as they can make the module graph more complex.
  • Avoid Split Packages: Ensure that a package is only defined in a single module. Split packages can lead to unexpected behavior.
  • Test Module Boundaries: Write integration tests to verify that modules interact correctly and that encapsulation is enforced.

Interview Tip

When discussing modules in interviews, be prepared to explain:

  • The motivation behind modules (Project Jigsaw).
  • The core concepts of modules (module declaration, encapsulation, dependencies, reliable configuration).
  • How modules improve security, maintainability, and performance.
  • Real-world use cases for modules.
  • The challenges of migrating to a modular architecture.

When to Use Modules

Modules are particularly beneficial in the following scenarios:

  • Large Projects: When dealing with large, complex projects with many dependencies.
  • Microservices Architectures: When building microservices, modules can help encapsulate each service and manage dependencies.
  • Library Development: When developing libraries that should expose a well-defined API and hide internal implementation details.
  • Applications with Security Requirements: When security is a concern, modules can help limit access to sensitive APIs.

Memory Footprint

Modules can reduce the memory footprint of Java applications by:

  • Loading Only Required Modules: The JVM only loads the modules that are explicitly required by the application, reducing the amount of code loaded into memory.
  • Optimizing Class Loading: The module system allows the JVM to optimize class loading, reducing the overhead of class verification and initialization.

Alternatives

Before Java 9, developers often used techniques like OSGi or custom class loaders to achieve some level of modularity. However, these approaches were often complex and had limitations.

Pros of Modules

  • Strong Encapsulation: Prevents unintended access to internal APIs.
  • Reliable Configuration: Ensures that all required modules are present at runtime.
  • Reduced Runtime Footprint: Reduces memory consumption by only loading necessary modules.
  • Improved Maintainability: Makes the codebase easier to understand and maintain.
  • Enhanced Security: Limits access to sensitive APIs.

Cons of Modules

  • Increased Complexity: Introduces a new level of complexity to the build and deployment process.
  • Migration Challenges: Migrating existing codebases to a modular architecture can be challenging.
  • Reflection Limitations: Modules impose restrictions on runtime reflection, which can affect frameworks that rely heavily on reflection.

FAQ

  • What is the requires transitive keyword?

    The requires transitive keyword means that if module A requires module B transitively, any module that requires A will also implicitly require B. This is useful when module B's API is an integral part of module A's API.

  • What are automatic modules?

    Automatic modules are created when a JAR file is placed on the module path without a module-info.java file. The module name is derived from the JAR file name. Automatic modules can access any other module but do not provide strong encapsulation.

  • How do I run a modular application?

    You can run a modular application using the java command with the --module-path and --module options. For example: java --module-path mods -m com.example.mymodule/com.example.mymodule.Main.