skip to content

Contrast bottom-up and top-down JPMS migration strategies and explain how automatic modules and the unnamed module enable incremental migration.

level: seniorimportance: should knowfreq 47%

answer

  1. Bottom-up = dependencies first (blocked on upstream)
  2. Top-down = your app first, libs as automatic modules
  3. Automatic module: named, exports all, requires all
  4. Unnamed module (classpath): reads all, can't be required
  5. Automatic-Module-Name manifest = stable name

basics

~20 s

Bottom-up means modularizing your dependencies first, then your own code; top-down means modularizing your own application first while leaving libraries as automatic modules. Automatic modules (plain jars on the module path) and the classpath unnamed module let you mix modular and non-modular code so you can migrate gradually.

solid answer

~50 s

JPMS supports incremental migration through two enablers. A plain jar with no module-info, when placed on the module path, becomes an automatic module: it gets a name (from the Automatic-Module-Name manifest entry or derived from the filename), exports all its packages, and requires/reads every other module. Code left on the classpath lives in the unnamed module, which reads everything but cannot be required by name. Bottom-up migration converts leaf dependencies into real modules first, working up toward your application — clean but blocked until libraries publish module-info. Top-down migration makes your own application a module immediately, treating still-unmodularized libraries as automatic modules via requires <auto-name>; it lets you start now but you depend on stable automatic names. In practice teams go top-down, pinning Automatic-Module-Name in their own jars to keep names stable, and converting libraries to explicit modules as upstream support arrives.

go deeper

for a junior

Knows that a plain jar can be put on the module path and used without writing module-info, enabling gradual adoption.

for a middle

Defines automatic vs unnamed module behavior and can place jars correctly to keep an app building during migration.

for a senior

Chooses top-down vs bottom-up deliberately, understands automatic-name fragility, and sets Automatic-Module-Name on owned artifacts.

for a principal

Plans an org's migration sequence across many repos, sets conventions (stable module names, module-path layout), and manages the dependency on upstream modularization timelines and risk.

## The migration problem When JPMS shipped (Java 9), the world's jars had no `module-info`. You cannot rewrite every dependency at once, so the module system was designed to **mix modular and non-modular code**. Two constructs make that possible. ### The unnamed module (classpath) Everything still loaded from the **classpath** is placed into a single **unnamed module**. It **reads every other module** (so classpath code can use any exported API) and **exports all its packages**, but it **has no name**, so a named module **cannot `requires` it**. This is why a real module can't depend on classpath-only code — it has nothing to name. ### Automatic modules (module path) If you put a **plain jar (no module-info) on the *module path***, it becomes an **automatic module**: - It gets a **name**: from the manifest entry **`Automatic-Module-Name`** if present, otherwise **derived from the jar filename** (dropping version, replacing non-alphanumerics with dots). - It **exports all of its packages**. - It **requires (reads) every other module**, including other automatic modules and the unnamed module. This lets an *explicit* module write `requires some.auto.module;` and start depending on a not-yet-modularized library today. ## Bottom-up migration **Bottom-up** = modularize the **leaves of the dependency graph first** (libraries with no further dependencies), then their dependents, finally your application. - *Pros:* by the time you write your own `module-info`, every dependency is a real module with a stable, declared name; the result is fully reliable. - *Cons:* you are **blocked on upstream** — you cannot finish until the libraries you use ship `module-info` (or you fork them). For most apps that is impractical at first. ## Top-down migration **Top-down** = modularize **your own application first**, leaving dependencies as **automatic modules** on the module path. - Your app gets a `module-info.java` with `requires <automatic-name>` for each library. - *Pros:* you can start **immediately**, gaining strong encapsulation for your own code without waiting on the ecosystem. - *Cons:* you depend on **automatic module names**. A filename-derived name can change when a library renames its artifact or adds a real `Automatic-Module-Name` later, breaking your `requires`. Mitigate by relying on libraries that set `Automatic-Module-Name`, and by **setting it yourself** in your own jars so downstream consumers get a stable name. ## Why top-down usually wins in practice Because you rarely control your dependencies, **top-down** is the pragmatic default: modularize what you own, treat the rest as automatic modules, and convert libraries to explicit modules opportunistically as upstream support lands. Publishing an `Automatic-Module-Name` in your own jar is the cheap, courteous first step that lets *your* consumers migrate top-down against a stable name. ## Key takeaways - Unnamed module = classpath bucket, reads all, **unnameable** (can't be required). - Automatic module = plain jar on the **module path**: named, exports all, requires all. - Bottom-up is cleanest but blocked on upstream; top-down starts now but rests on automatic-name stability. - The smallest useful migration act: add `Automatic-Module-Name` to your manifest.

  • Why can't an explicit module declare a dependency on code that is only on the classpath?
    Classpath code lives in the unnamed module, which has no name. requires needs a module name to reference, so there is nothing to require. The code must move onto the module path (becoming an automatic module) or be modularized.
  • What risk does relying on a filename-derived automatic module name create?
    If the library later renames its artifact or adds an explicit Automatic-Module-Name, the derived name changes and your requires clause breaks. Prefer libraries that declare Automatic-Module-Name, since that name is stable across releases.

Renovating a house: bottom-up is fixing the foundation and plumbing before the rooms (correct but you can't move in yet); top-down is finishing your own rooms first while the shared utilities stay 'as-is' temporarily.

saying these in an interview costs you the question

  • Saying an automatic module exports only some packages — it exports all
  • Claiming the unnamed module can be required by name
  • Thinking a plain jar on the classpath is an automatic module — it must be on the module path
  • Believing bottom-up is always preferable despite upstream blocking
  • Confusing Automatic-Module-Name (stable) with filename-derived names (fragile)

context