What are the three categories of modules in the Java Platform Module System, and what defines each one?
answer
- Three kinds: named (module-info), automatic (plain JAR on module path), unnamed (classpath)
- Automatic name = Automatic-Module-Name header, else from filename
- Automatic + unnamed export all & read all
- Named module CANNOT read the unnamed module
- Migration ladder: classpath -> module path -> module-info
basics
~20 sJPMS 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).
solid answer
~40 sJPMS sorts code into three module kinds. A named module is a JAR (or directory) that contains a compiled module-info, which explicitly declares the module's name, the packages it exports, and the modules it requires; it gets real encapsulation. An automatic module is a plain, non-modular JAR that you nevertheless put on the module path: the system invents a module name for it (from the Automatic-Module-Name manifest header, or failing that the JAR filename), and it implicitly exports all its packages and reads every other module. The unnamed module is the catch-all: all classes loaded from the classpath belong to it; it reads everything and exports everything, giving the pre-Java-9 'no encapsulation' behaviour. The split lets new modular code and legacy classpath code coexist during migration.
code
java · 13 lines// Named module: src/com.acme.payments/module-info.java
module com.acme.payments {
requires com.acme.core; // explicit dependency
exports com.acme.payments.api; // only this package is visible
}
// Automatic module name pinned via MANIFEST.MF of a plain JAR:
// Automatic-Module-Name: org.acme.legacy
// (else 'legacy-1.2.jar' would derive the module name 'legacy')
// Unnamed module: anything launched with
// java -cp libs/*:app.jar com.acme.Main
// All those classes live in the unnamed module (reads all, exports all).go deeper
Can list the three categories and say named modules have a module-info while classpath code is the unnamed module.
Explains how each category is created (module-info vs module path vs classpath) and that automatic/unnamed read and export everything.
Articulates the migration story, how automatic module names are derived (manifest then filename), and the named-cannot-read-unnamed rule with its practical consequence.
Reasons about top-down vs bottom-up migration strategy, the stability risk of filename-derived names across an org, and trade-offs of pushing dependencies onto the module path.
## Background: what is a module at all? Before Java 9 the runtime had only the **classpath** — a flat list of JARs and folders. Every public class in every JAR was visible to every other class, there was no notion of which JAR depended on which, and missing dependencies only surfaced as `NoClassDefFoundError` at runtime. The **Java Platform Module System (JPMS)**, introduced in Java 9 (project Jigsaw), adds a layer above packages: a **module** is a named, self-describing group of packages that declares (a) what it **exports** (which of its packages other code may use) and (b) what it **requires** (which other modules it depends on). This gives *strong encapsulation* (non-exported packages are truly hidden) and *reliable configuration* (missing dependencies fail fast at startup). Crucially, JPMS had to be adoptable gradually — billions of lines of existing classpath code could not be rewritten overnight. To make that possible, the runtime recognises **three categories of module**. ## 1. Named module (explicit module) A **named module** is the 'real' module. It is a JAR (or an exploded directory) that contains a compiled `module-info.class`, produced from a `module-info.java` source file at the root of the module. That descriptor states the module's **name** and its rules: ```java module com.acme.payments { requires com.acme.core; // I depend on this module exports com.acme.payments.api; // others may use this package } ``` A named module gets the full JPMS treatment: only the packages it `exports` are accessible to other modules, it can only see modules it `requires` (plus the implicit `java.base`), and the dependency graph is validated when the module graph is resolved. (There is a sub-flavour, the *open module* / `opens` directive, that grants deep reflective access — but it is still a named module.) Named modules are how you get encapsulation and explicit dependencies. ## 2. Automatic module Reality check: most libraries on Maven Central still ship as plain JARs with **no** `module-info`. If you want to *use* JPMS for your own code but still depend on such a library, you place that plain JAR on the **module path** (the `--module-path` / `-p` argument) instead of the classpath. The runtime then treats it as an **automatic module**. An automatic module is a bridge with three special behaviours: - **Auto-derived name.** Since the JAR has no declared name, the system makes one up. If the JAR's `MANIFEST.MF` contains an `Automatic-Module-Name:` header, that value is the module name (this is the *stable* way library authors should opt in). Otherwise the name is derived from the **filename**: the version suffix is stripped and non-alphanumeric runs become dots, so `guava-32.1.jar` becomes module `guava`. Filename-derived names are fragile (renaming the file changes the module name), which is why the manifest header is preferred. - **Exports everything.** An automatic module implicitly exports *all* of its packages, so its whole public API is reachable — there is no encapsulation. - **Reads everything.** An automatic module implicitly `requires` every other module in the graph, *including the unnamed module*. That last point is the key trick: it lets the legacy JAR keep finding its own dependencies that are still on the classpath. Automatic modules are explicitly a **migration aid** — a stepping stone so you can modularise your application top-down even while your dependencies are not yet modularised. ## 3. The unnamed module Everything loaded from the **classpath** (the `-classpath` / `-cp` path) is bundled into a single, special **unnamed module** — one per class loader. It behaves like pre-Java-9 code: - It has **no name** (so nothing can `requires` it by name). - It **reads every other module** in the graph, so classpath code can use any modular library. - It **exports all its packages**, so its public types are visible to other classpath code. The unnamed module is how the old world keeps working: drop your app on the classpath and it behaves exactly as before. ## The asymmetry rule (the gotcha) There is a deliberate one-way rule: **a named module cannot read the unnamed module.** Because the unnamed module has no name, a `module-info` literally cannot write `requires <unnamed>`. So if you fully modularise a piece of code (give it a `module-info`) but leave one of its dependencies on the classpath, that dependency now lives in the unnamed module and your named module *cannot see it* — you get a compile/resolution error. The fix is to move that dependency onto the module path (where it becomes an automatic or named module). Note the asymmetry: automatic modules *can* read the unnamed module (that's why they exist), but proper named modules cannot. This is the single most common surprise during migration. ## How they fit together Think of it as a migration ladder: classpath code (unnamed module, no rules) → put a plain JAR on the module path (automatic module, gets a name but no encapsulation) → add a `module-info` (named module, full encapsulation and explicit dependencies). The three categories let a codebase sit anywhere on that ladder and still run.
- Where does a plain JAR end up if you leave it on the classpath instead of the module path?In the unnamed module — it behaves like pre-Java-9 classpath code (reads all, exports all), and it does NOT become an automatic module.
- Why is the Automatic-Module-Name manifest header preferred over the filename-derived name?The filename-derived name changes if the file is renamed (e.g. version bump), so downstream requires can break; the manifest header pins a stable, intentional name regardless of filename.
saying these in an interview costs you the question
- Saying a plain JAR on the *classpath* becomes an automatic module — it joins the *unnamed* module; automatic requires the *module path*
- Claiming a named module can read the unnamed module — it cannot (no name to require)
- Thinking automatic modules encapsulate their packages — they export everything
- Confusing module path with classpath
- Believing every JAR must have module-info to run on Java 9+