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.jar→guava), unless the JAR's manifest setsAutomatic-Module-Name. - Exports every package — it has no
exportsdirective, 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()returnsnull,isNamed()isfalse). - 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 → | Named | Automatic | Unnamed |
|---|---|---|---|
| Named | only if requires | only if requires | never |
| Automatic | yes | yes | yes |
| Unnamed | yes | yes | yes |
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.
What to take from the run:
- The program's own class classified as UNNAMED with a
nullname, whilejava.util.ListandHttpClientclassified 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)returnedfalsebutself.canRead(java.base)returnedtrue. 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 cannotrequiresclasspath code.classify()distinguished automatic from named bydescriptor.isAutomatic(). You will not seetruehere (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.baseexportedjava.util(true) but notjdk.internal.misc(false), even though both are real packages inside it. A named module'sexportslist is an allow-list; unexported packages are invisible regardless of beingpublic. The unnamed module, by contrast, exports everything it contains.- No
module-info.javawas needed to observe any of this. The three categories are runtime facts about how a class was loaded, andgetModule()plusgetDescriptor()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
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?