Module Descriptor (module-info.java)

A module descriptor, written in `module-info.java`, defines a named Java module and declares its dependencies, exported packages, services, and related module metadata. It is the central declaration used by the Java Platform Module System (JPMS) to describe a module's boundaries and relationships.

A module descriptor is a special Java source file named `module-info.java`. It contains a `module` declaration that gives the module its name and can specify directives controlling dependencies and access between modules.

The `requires` directive declares that the module depends on another module. The `exports` directive makes a package accessible to other modules; packages that are not exported remain encapsulated from ordinary compile-time and runtime access by other named modules.

A descriptor can also declare services with `uses` and `provides ... with ...`. `uses` identifies a service that the module consumes, while `provides` identifies an implementation supplied by the module. Other directives include `requires transitive`, `requires static`, and qualified forms such as `exports ... to`.

For a named module, the descriptor provides information used by the compiler and module system to resolve dependencies and enforce strong encapsulation. A module can therefore expose a deliberate public API rather than automatically making every public class in every package available outside the module.

Key points

Interview questions

What is `module-info.java` used for?

`module-info.java` is the module descriptor used by JPMS. It gives a module its name and declares relationships such as required modules and exported packages. It can also describe consumed and provided services. The Java compiler and module system use this metadata to resolve dependencies and enforce module boundaries.

What is the difference between `requires` and `exports`?

`requires` describes a dependency that the current module needs. `exports` describes a package that the current module makes available to other modules. They control opposite directions of the module relationship: one declares what the module reads, while the other declares what it exposes. A module can require another module without exporting any of its own packages.

What happens if a package is not exported?

In a named module, a package that is not exported is not normally accessible to code in other named modules through ordinary Java access. Its public types can still be used internally by the defining module. This lets a module separate its implementation packages from its supported API packages. Reflection has additional rules, which is why `opens` is relevant for frameworks requiring reflective access.

What is the purpose of `requires transitive`?

`requires transitive` declares a dependency that should also be readable by modules that read the declaring module. It is useful when the declaring module's public API exposes types from that dependency, creating an API-level dependency. A normal `requires` keeps the dependency specific to the module that declares it. The choice should reflect whether downstream users need the dependency to compile against the exposed API.

How do JPMS services work with `uses` and `provides`?

A module that consumes a service can declare `uses` for the service interface or abstract type. A module that supplies an implementation can declare `provides Service with Implementation`. This allows service discovery through mechanisms such as `ServiceLoader` without the consumer directly depending on a particular implementation class. The service declarations therefore support loose coupling between consumers and providers.