W3docs

Java Module Types

Named, automatic, and unnamed modules in Java and how they interact during compilation and execution.

Java Module Types

JPMS recognises three kinds of module. Only one is the "real" thing you author; the other two exist so that the millions of pre-Java-9 JARs keep working. Understanding which kind a given JAR becomes — and that depends entirely on where you put it — is the key to migrating without pain.

Named modules

A named (explicit) module is one with a module-info.class, placed on the module path (--module-path / -p). It is the full citizen:

  • It has a name from its descriptor.
  • It reads only the modules it requires.
  • It exposes only the packages it exports.

This is the strongly-encapsulated module the previous chapters described. Everything JPMS promises — declared dependencies, hidden internals, fail-fast resolution — applies to named modules.

Automatic modules

An automatic module is a plain JAR (no module-info) placed on the module path. JPMS wraps it in a module so that named modules can requires it during migration — without waiting for the library author to add a descriptor. An automatic module:

  • Gets a name derived from the JAR file name (e.g. guava-32.1.jarguava), unless the JAR's manifest sets Automatic-Module-Name.
  • Exports every package — it has no exports directive, so all of its packages are open to the world.
  • Reads every other module, including the unnamed module, so it can still see classpath JARs.

It is a bridge: it lets you start writing named modules that depend on not-yet-modularized libraries. The cost is that it gives up encapsulation entirely, and its auto-derived name may change if the JAR is renamed — which is why Automatic-Module-Name in the manifest is the responsible thing for a library to ship.

The unnamed module

The unnamed module is the catch-all for the classpath. Every class loaded from the classpath belongs to its class loader's unnamed module. It:

  • Has no name (getName() returns null, isNamed() is false).
  • Reads every other module on the system.
  • Exports all of its packages to other unnamed/automatic modules.

But there is a deliberate one-way wall: a named module cannot requires the unnamed module. You cannot name it, so you cannot depend on it. This is the rule that forces migration order — a named module may only depend on other named or automatic modules, never on raw classpath code.

The access matrix

Who can read whom comes down to a small table:

From ↓ / To →NamedAutomaticUnnamed
Namedonly if requiresonly if requiresnever
Automaticyesyesyes
Unnamedyesyesyes

The single restrictive cell — named code cannot reach unnamed code — is the whole story of why you migrate bottom-up (covered next chapter).

A worked example: identifying a module's kind at runtime

The Module API tells you, for any class, whether its module is named and whether it was synthesised automatically. This program inspects three references — its own class (classpath → unnamed), a JDK type (named), and reports the boot layer — to make the categories concrete.

java— editable, runs on the server

What to take from the run:

  • The program's own class classified as UNNAMED with a null name, while java.util.List and HttpClient classified as NAMED (java.base, java.net.http). Run from the classpath, your code is always unnamed; the JDK is always a set of named modules. The kind of a module is decided by how it was loaded, not by anything in the class itself.
  • java.base.canRead(self) returned false but self.canRead(java.base) returned true. That is the one-way wall in action: the unnamed module reads everything, but no named module reads the unnamed module. This asymmetry is precisely why named code cannot requires classpath code.
  • classify() distinguished automatic from named by descriptor.isAutomatic(). You will not see true here (nothing was placed on the module path as a plain JAR), but the check is exactly how tooling reports an automatic module — a real module object with a synthesised, fully-open descriptor.
  • java.base exported java.util (true) but not jdk.internal.misc (false), even though both are real packages inside it. A named module's exports list is an allow-list; unexported packages are invisible regardless of being public. The unnamed module, by contrast, exports everything it contains.
  • No module-info.java was needed to observe any of this. The three categories are runtime facts about how a class was loaded, and getModule() plus getDescriptor() expose them — the same calls migration tools rely on to figure out what they are working with.

Why three kinds exist

The two compatibility kinds — automatic and unnamed — mean Java 9+ runs unmodified Java 8 applications. You opt into strong encapsulation one JAR at a time: leave everything on the classpath (all unnamed) and nothing changes; move a library to the module path without a descriptor and it becomes automatic; add a module-info.java and it becomes named. The next chapter looks at the uses/provides services that decouple modules, then the final chapter sequences a real migration through these three states.

Practice

Practice

During migration you place a plain third-party JAR (no 'module-info.class') named 'fastjson-2.0.jar' onto the MODULE PATH, then write 'requires fastjson;' in your own named module. Which statement correctly describes 'fastjson' here?