What is the difference between the classpath and the module path, and how does the same plain JAR behave on each?
answer
- -cp / classpath = flat, no encapsulation, all -> unnamed module
- -p / --module-path = each entry is a module, resolved & encapsulated
- Plain JAR: classpath -> unnamed module; module path -> automatic module
- module-info is IGNORED on the classpath
- You can use both paths together during migration
basics
~20 sThe 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 sThere 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
Knows -cp is the classpath and -p is the module path, and that the same JAR is treated differently on each.
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.
Connects the two paths to JPMS guarantees (reliable configuration, strong encapsulation), reasons about mixed-path migration, and recalls the named-cannot-read-unnamed constraint.
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