Java module-info.java: Learn Module Descriptors, Requires, Exports, and Opens

0

A Java module needs a formal description of its identity, dependencies, and exported packages. This information is written in a special source file named module-info.java.

You can think of module-info.java as the module's declaration card. Instead of discovering the module's structure by inspecting every package and class, Java can read this single descriptor to understand how the module is intended to interact with the rest of the application.

What Is module-info.java?

module-info.java is the module descriptor introduced with the Java Platform Module System in Java 9. It defines the module name and can declare dependencies, exported packages, opened packages, services, and other module-level rules.

module com.example.library {
}

This is the simplest form of a module descriptor. It declares a module named com.example.library without declaring any dependencies or exported packages.

Important: The file name must be exactly module-info.java. It is not an ordinary class and does not contain a class declaration.

Why Does module-info.java Exist?

Before Java 9, Java applications primarily relied on packages, classpaths, and JAR files. Dependencies were often discovered through build configuration and classpath setup rather than being formally declared by the Java language itself.

The module descriptor provides a clear architectural contract.

Declaration Purpose
module Declares the module's name.
requires Declares a dependency on another module.
exports Makes a package accessible to other modules.
opens Allows reflective access to a package.
uses Declares that the module consumes a service.
provides Declares an implementation of a service.

Basic Structure

A module descriptor can contain several directives inside the module declaration.

module com.example.application {
    requires com.example.library;
    exports com.example.application.api;
}

This module declares itself as com.example.application, requires com.example.library, and exports its api package.

Declaring the Module Name

Every named module begins with the module keyword followed by its module name.

module com.example.library {
}

The module name should be meaningful and follow a consistent naming convention. Organization-owned modules commonly use names based on the organization's namespace.

The requires Directive

The requires directive declares that one module depends on another module.

module com.example.application {
    requires com.example.library;
}

Here, the application module declares a dependency on the library module.

If code inside the application uses public types from the library's exported packages, the required module must be available through the module system.

Multiple Dependencies

A module can require several other modules.

module com.example.application {
    requires com.example.library;
    requires java.sql;
    requires java.logging;
}

Each dependency is declared separately. This makes the module's external requirements visible in one place.

The exports Directive

The exports directive makes a package accessible to other modules.

module com.example.library {
    exports com.example.library.api;
}

Suppose the module contains these packages:

com.example.library.api
com.example.library.internal

Only the api package is exported. The internal package remains encapsulated at the module level.

Remember: public and exports solve different problems. A public class can be accessible within its module, but code in another module also needs the containing package to be exported.

Exporting Multiple Packages

A module can export several packages.

module com.example.library {
    exports com.example.library.api;
    exports com.example.library.model;
}

Both packages are then part of the module's exported surface.

Exports Are Package-Based

The exports directive works at the package level rather than individual class level.

module com.example.library {
    exports com.example.library.api;
}

This means the exported package can contain multiple public classes. The module descriptor does not need a separate exports line for every class.

The opens Directive

Java modules also provide the opens directive. It is mainly related to reflection.

module com.example.application {
    opens com.example.application.model;
}

An opened package allows deep reflective access according to the module system's rules. This can be important for frameworks that inspect classes and fields at runtime.

The important distinction is that exports is about normal access to public types, while opens is primarily about reflective access.

Directive Main Purpose
exports Allows normal access to public types in a package from other modules.
opens Allows reflective access to package contents according to module rules.

Qualified Exports

Java can also export a package only to selected modules.

module com.example.library {
    exports com.example.library.api
        to com.example.application;
}

This is called a qualified export. It provides more precise control over which modules can access the exported package.

Qualified Opens

The same idea can be applied to reflective access using a qualified opens directive.

module com.example.application {
    opens com.example.model
        to com.example.framework;
}

This allows the specified package to be opened specifically to the named module rather than broadly to every module.

Automatic java.base Dependency

Every named Java module implicitly depends on the java.base module. Therefore, you normally do not need to write:

requires java.base;

The dependency on java.base is automatically available to named modules.

Remember: Classes from fundamental packages such as java.lang are available because java.base is implicitly required by every named module.

Complete Basic Example

Consider a library module that exposes an API package and depends on the Java SQL module.

module com.example.library {
    requires java.sql;
    exports com.example.library.api;
}

The descriptor communicates three important facts: the module is named com.example.library, it requires java.sql, and it exposes com.example.library.api.

Module Descriptor with Multiple Directives

A more advanced module descriptor can combine several directives.

module com.example.application {
    requires com.example.library;
    requires java.sql;

    exports com.example.application.api;
    opens com.example.application.model;
}

This structure is common in larger modular applications where the public API and framework-facing implementation details need different visibility rules.

Where Is module-info.java Placed?

The module descriptor is placed at the root of the module's source structure.

src/
└── com.example.application/
    ├── module-info.java
    └── com/
        └── example/
            └── application/
                ├── Main.java
                └── api/
                    └── ApplicationService.java

The exact source layout can vary depending on the build tool, but module-info.java represents the module at the module source root.

Common Beginner Mistakes

  • Treating module-info.java as a normal Java class.
  • Forgetting the module declaration.
  • Assuming public classes are automatically visible outside their module.
  • Confusing exports with opens.
  • Explicitly declaring requires java.base even though it is implicit.
  • Forgetting to declare required named modules.

Best Practices

  • Keep the module descriptor focused on architectural dependencies and boundaries.
  • Export only packages that are intentionally part of the module's public API.
  • Use opens only when reflective access is actually required.
  • Declare module dependencies explicitly with requires.
  • Keep internal implementation packages unexported whenever possible.

Interview Insight

A common interview question is, "What is module-info.java?" A strong answer is: It is the module descriptor introduced with Java 9 that declares a module's name, dependencies, exported packages, reflective access rules, and service relationships.

Interview Tip: Remember the three directives most commonly discussed with beginners: requires declares dependencies, exports exposes packages for normal access, and opens enables reflective access.

Quick Revision

Directive Purpose Example
module Declares the module name. module com.example.app { }
requires Declares a module dependency. requires java.sql;
exports Exposes a package for normal external access. exports com.example.api;
opens Allows reflective access to a package. opens com.example.model;
java.base Implicit dependency of every named module. No explicit declaration normally required.

module-info.java is the central declaration point for a named Java module. Once you understand its directives, the module system becomes much less mysterious: dependencies are declared with requires, public package boundaries with exports, and reflective requirements with opens. The next chapter builds on this foundation by exploring the module path and how Java locates modules at compile time and runtime.

Post a Comment

0Comments
Post a Comment (0)