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.
