Walk through how jdeps, jmod, and jlink work together to ship a minimal runtime, and what breaks the chain.
answer
- Pipeline: jdeps (analyze) -> jmod (package native-bearing module) -> jlink (link image)
- jdeps --generate-module-info bootstraps modularizing legacy JARs
- Breaker 1: non-modular classpath JARs aren't linkable
- Breaker 2: reflection/ServiceLoader invisible to static jdeps
- Fix: --add-modules, uses/provides + --bind-services, opens, runtime test
basics
~20 sjdeps analyzes your code to find its dependencies and can draft a module descriptor; jmod packages a module (including native code) into a .jmod; jlink links your modules and their dependencies into a small, self-contained runtime image. The chain breaks mainly on non-modular libraries and on dependencies resolved by reflection.
solid answer
~50 sThe pipeline is analyze -> package -> link. jdeps reads bytecode to map your dependencies and, via --generate-module-info, drafts module-info files so non-modular code can become modular. jmod packages a module — including native libraries, commands, and config a JAR can't hold — into a .jmod consumed at link time. jlink takes the module path plus a root --add-modules set, resolves the transitive closure, and emits a trimmed runtime image (JVM + only needed modules). What breaks it: (1) plain classpath JARs aren't modules, so they must be modularized, treated as automatic modules, or skipped; (2) reflective access — Class.forName, ServiceLoader, frameworks — is invisible to jdeps's static analysis, so required modules can be missing from the image and fail at runtime. You compensate by adding modules explicitly (--add-modules), declaring requires/uses, and runtime-testing the image rather than trusting the static report.
go deeper
Knows the three tools exist and roughly that one analyzes, one packages, and one builds the small runtime.
Can describe the analyze->package->link flow and name the non-modular-JAR problem, and run jdeps then jlink for a simple modular app.
Explains both failure modes (non-modular deps, reflection/services), applies fixes (modularize, --add-modules, uses/provides + --bind-services, opens) and insists on runtime-testing the image.
Owns the migration/packaging strategy for a large dependency graph with native and reflective libraries, decides modularization-vs-classpath tradeoffs, and bakes static + runtime validation into the release pipeline.
## The end-to-end pipeline The three JPMS tools form a deliberate **analyze → package → link** pipeline. ### 1. jdeps — analyze `jdeps` statically reads your compiled bytecode and reports what packages/modules you depend on. Two pipeline-relevant uses: - `jdeps --jdk-internals app.jar`: confirm you aren't using soon-to-be-removed internal APIs. - `jdeps --generate-module-info out/ lib.jar`: emit a **draft `module-info.java`**, turning a legacy classpath JAR into a candidate module. You then refine it (tighten `exports`, add `requires transitive`, add `opens` for reflective access). ### 2. jmod — package For a module that needs more than classes — **native libraries** (`--libs`), **native commands** (`--cmds`), **config** (`--config`) — you use `jmod create` to produce a `.jmod`. A pure-bytecode module can stay a **modular JAR** instead. Either form is acceptable input to the next step; `.jmod` is link-time only and can't be run directly. ### 3. jlink — link `jlink --module-path <jdk-jmods + your-modules> --add-modules <roots> --output image` resolves the **transitive closure** from your root modules and assembles a self-contained runtime image: a trimmed JVM plus exactly those modules, optionally optimized with `--strip-debug`, `--compress`, `--launcher`, etc. ## Why the chain breaks — two classic failures ### Failure A: non-modular dependencies jlink links **modules**. A plain classpath JAR with no `module-info` is not a module. Options: - **Modularize it** (add a `module-info`, recompile). - Use it as an **automatic module** (a JAR on the module path gets a name derived from its filename/manifest) — but automatic modules `requires` the *entire* world and **cannot be jlinked** as-is in many setups, so this is a development convenience, not a clean link input. - Generate a descriptor with `jdeps --generate-module-info` and recompile. If you skip this, jlink can't include the dependency and resolution fails. ### Failure B: reflection and dynamic resolution jdeps sees only **static, compile-baked references**. Anything resolved at runtime is invisible: - `Class.forName("...")` and reflective instantiation. - **ServiceLoader** / the `provides`/`uses` mechanism (JDBC drivers, logging providers). - Framework-driven proxies (Spring, Hibernate) and JSON binders that reflect over your types. Consequences: a module that's only reached reflectively may be **omitted from the image** (it never showed up in jdeps), or it's present but the access fails with `InaccessibleObjectException` because the package wasn't `opens`-ed. Compensations: - Add the module explicitly with `--add-modules` at link time. - Declare `uses`/`provides` for services so `--bind-services` (or default service binding) pulls providers in. - Add `opens`/`open module` for reflective frameworks. - **Runtime-test the linked image** — a clean static analysis never proves runtime correctness. ## The mental model jdeps answers *"what do I depend on, and is any of it dangerous (internal APIs)?"*; jmod answers *"how do I package this module including its native bits?"*; jlink answers *"give me the smallest runtime that can run exactly this."* The two things that defeat the pipeline are **un-modularized code** and **dependencies the compiler never recorded** — both stem from the same root cause: static tooling can only act on what is statically present.
- A service provider (e.g. a JDBC driver) is missing from your jlink image. Why, and how do you fix it?It's pulled in via ServiceLoader, which is dynamic and invisible to static resolution. Declare uses/provides in the module descriptors and enable service binding (--bind-services or add the provider with --add-modules) so jlink includes it.
- Why are automatic modules a poor input to jlink?An automatic module has no real descriptor: it reads every other module and exports everything, so it can't be reliably resolved/linked. jlink generally rejects/struggles with them — modularize properly first.
- What single root cause explains both pipeline-breaking failures?Static tooling can only act on what is statically present in bytecode/descriptors: un-modularized code has no descriptor to link, and reflective dependencies were never recorded at compile time.
saying these in an interview costs you the question
- Assuming a clean jdeps run guarantees the jlink image runs correctly
- Trying to jlink automatic modules / plain classpath JARs without modularizing
- Forgetting that reflective/service dependencies must be added explicitly
- Mixing up which tool does what (analysis vs packaging vs linking)