skip to content

Named, Automatic & Unnamed Modules

Named modules have a module-info, automatic modules are plain JARs on the module path that read and export everything, and the unnamed module is the classpath. The rule that a named module cannot read the unnamed module is what makes migration awkward.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

5

What is the difference between the classpath and the module path, and how does the same plain JAR behave on each?

level: juniorimportance: must knowfreq 50%

answer

  1. -cp / classpath = flat, no encapsulation, all -> unnamed module
  2. -p / --module-path = each entry is a module, resolved & encapsulated
  3. Plain JAR: classpath -> unnamed module; module path -> automatic module
  4. module-info is IGNORED on the classpath
  5. You can use both paths together during migration

basics

~20 s

The classpath (-cp) is the old flat list of JARs where everything is visible to everything; code there forms the unnamed module. The module path (-p) is the JPMS list where each JAR is a module. A plain JAR is part of the unnamed module on the classpath, but an automatic module on the module path.

solid answer

~50 s

There are two ways to tell the JVM where your code lives. The **classpath** (`-cp` / `-classpath`) is the pre-Java-9 mechanism: a flat list of JARs and folders with no dependency information, where every public type is visible to every other — all of it collected into the single **unnamed module**. The **module path** (`--module-path` / `-p`) is the JPMS mechanism: each entry is treated as a module, dependencies are resolved up front, and encapsulation is enforced. The same plain (non-modular) JAR behaves differently depending on which path you use: on the classpath it joins the **unnamed module** (reads all, exports all, no name); on the module path it becomes an **automatic module** (gets a derived or manifest name, still reads all and exports all, but now *has a name* so named modules can `requires` it). A JAR with a `module-info` becomes a full **named module** on the module path. You can even use both paths at once during migration.

go deeper

for a junior

Knows -cp is the classpath and -p is the module path, and that the same JAR is treated differently on each.

for a middle

Explains the unnamed-vs-automatic distinction by path, that module-info is ignored on the classpath, and that the module path resolves dependencies up front.

for a senior

Connects the two paths to JPMS guarantees (reliable configuration, strong encapsulation), reasons about mixed-path migration, and recalls the named-cannot-read-unnamed constraint.

for a principal

Defines build/run policy for mixed-path migrations, weighs fail-fast resolution benefits, and guides teams on when to push artifacts from classpath to module path.

## Two switches, two worlds When you launch Java you tell it where to find compiled code with one (or both) of two options: - **Classpath:** `java -cp libs/a.jar:libs/b.jar Main` (or `-classpath`, or the `CLASSPATH` env var). This is the original, pre-Java-9 mechanism. - **Module path:** `java -p mods -m com.acme.app/com.acme.Main` (or `--module-path`). This is the JPMS mechanism added in Java 9. They look similar (both are lists of JARs/folders) but the runtime treats them completely differently. ## The classpath: the flat, anonymous world On the classpath there is **no concept of a module boundary**. The JVM scans all the listed JARs/folders and makes every public class visible to every other — there is no `requires`, no `exports`, no encapsulation. Missing dependencies aren't detected until a class is actually needed (you get a `NoClassDefFoundError` at runtime). All of this classpath code is bundled into one special pseudo-module, the **unnamed module** (one per class loader). The unnamed module has **no name**, **reads every module**, and **exports all its packages** — i.e. exactly the old behaviour. The classpath still exists in modern Java specifically so legacy applications keep running unchanged. ## The module path: the named, validated world On the module path, **each entry is a module**. At startup the runtime *resolves* the module graph: it reads each module's declared `requires`, finds the providers, and **fails fast** if a required module is missing — before any application code runs. It then enforces encapsulation: a module can only access packages another module `exports`, and can only see modules it `requires`. This gives the two headline JPMS benefits — *reliable configuration* and *strong encapsulation*. ## The same JAR, two behaviours (the key idea) What the path determines is the **category** a JAR falls into: | JAR contents | On the classpath | On the module path | |---|---|---| | Plain JAR (no module-info) | part of the **unnamed module** (no name) | **automatic module** (derived/manifest name, exports & reads all) | | JAR with `module-info` | part of the **unnamed module** — its module-info is *ignored* | **named module** (full encapsulation, explicit requires/exports) | The practical punchlines: - A plain JAR doesn't *become* an automatic module just by existing on Java 9+ — it only does so **on the module path**. On the classpath it's just unnamed-module code. - Even a fully modular JAR (with `module-info`) is treated as plain unnamed-module code if you put it on the **classpath** — the `module-info` is simply not honoured there. You only get module behaviour on the module path. ## Mixing both during migration You are allowed to use the classpath and module path **at the same time**. A typical migration run puts your modularised app and its modular/automatic dependencies on the module path while leaving some legacy bits on the classpath. Remember the asymmetry, though: **named modules can't read the unnamed module**, so any classpath dependency a named module needs must be moved to the module path. Automatic modules, by contrast, *can* read the unnamed module, which is what lets a half-migrated app keep functioning. ## Mental model Classpath = the old town with no street signs: everyone can walk into everyone's house, and you only discover a missing house when you try to visit it. Module path = the new town with named addresses and a delivery manifest: addresses are checked up front, and you can only enter rooms (packages) a house chooses to open. The *same building* (JAR) gets a street address only when it's registered in the new town (placed on the module path).

  • What happens to a JAR that has a module-info if you put it on the classpath instead of the module path?
    Its module-info is ignored; it behaves as ordinary classpath code inside the unnamed module, with no encapsulation and no enforced requires — exactly like a plain JAR there.
  • How does the runtime react to a missing dependency on the module path versus the classpath?
    On the module path, the missing required module is detected at startup (fast failure during resolution); on the classpath the problem surfaces only at runtime as NoClassDefFoundError when the class is first touched.

saying these in an interview costs you the question

  • Thinking a plain JAR becomes an automatic module on the classpath (it joins the unnamed module)
  • Assuming a module-info JAR is encapsulated on the classpath (its module-info is ignored there)
  • Believing classpath was removed in Java 9 — it still works
  • Conflating -cp and -p as the same flag

context

open as a page

What are the three categories of modules in the Java Platform Module System, and what defines each one?

level: middleimportance: must knowfreq 62%

basics

~20 s

JPMS has three kinds of modules: named modules (a JAR with a module-info.java declaring its name, exports, and requires), automatic modules (a plain JAR placed on the module path, getting an auto-derived name), and the unnamed module (everything on the old classpath).

open as a page

How does an automatic module get its name, and why is relying on the filename-derived name risky?

level: middleimportance: should knowfreq 40%

basics

~20 s

An automatic module's name comes from the Automatic-Module-Name entry in the JAR's MANIFEST.MF if present. If not, Java derives it from the JAR filename (dropping the version and turning non-letters/digits into dots). Filename-derived names are risky because renaming the file changes the module name.

open as a page

Why can a named module not read the unnamed module, and how does this affect migrating an app to JPMS?

level: seniorimportance: should knowfreq 45%

basics

~20 s

The unnamed module has no name, and a module-info file declares dependencies by name (requires X). With no name to write, a named module literally cannot require the unnamed module, so it cannot see any code left on the classpath.

open as a page

During a JPMS migration, what problems arise from automatic modules and split packages, and how do you reason about them?

level: principalimportance: nice to knowfreq 28%

basics

~20 s

Automatic modules read and export everything, so they give no real encapsulation and their derived names can be unstable. JPMS also forbids the same package being supplied by two modules (a split package), which legacy JARs often violate — that causes resolution failures you must resolve by merging or relocating packages.

open as a page