Explain the JPMS escape-hatch flags --add-exports, --add-opens, --add-modules, and --add-reads. When is each needed during migration?
answer
- exports=use public API; opens=deep reflection
- add-modules=force resolution (e.g. removed java.xml.bind)
- add-reads=ad-hoc requires edge
- ALL-UNNAMED targets classpath code
- Manifest Add-Opens/Add-Exports; JDK_JAVA_OPTIONS; argfile
- Bridges, not architecture — review every upgrade
basics
~20 sThese command-line flags relax module rules without editing module-info. --add-exports lets you use a package that isn't exported; --add-opens grants deep reflection into a package; --add-modules forces a module into the graph even if nothing requires it; --add-reads lets one module read another it didn't declare. They are temporary bridges during migration.
solid answer
~50 sJPMS provides four key escape hatches to relax encapsulation and resolution without changing module descriptors. --add-exports <module>/<package>=<target> makes a non-exported package's public types accessible (compile and run) — for using internal or unexported API. --add-opens <module>/<package>=<target> additionally grants deep reflective access (setAccessible on private members) — frameworks doing serialization, DI, or mocking need this. --add-modules <module> adds a module to the root set so it is resolved even when no requires references it — needed for modules removed from the default set (like the old java.xml.bind / Java EE modules dropped in Java 11) or service providers discovered only at runtime. --add-reads <source>=<target> adds a readability edge so source can read target without a requires clause. Use ALL-UNNAMED as a target to grant access to classpath code, ALL-SYSTEM where applicable. They can live on the command line, in JDK_JAVA_OPTIONS, or jar manifest entries (Add-Exports/Add-Opens). Treat all four as transitional bridges, not permanent architecture, and prefer real module-info declarations or supported APIs once available.
go deeper
Recognizes that these flags relax module restrictions and can keep an app running after an upgrade.
Correctly maps each flag to its purpose and applies --add-opens for a reflection failure or --add-modules for a missing JDK module.
Distinguishes the four precisely, knows the java.xml.bind removal story, and uses manifest entries / JDK_JAVA_OPTIONS appropriately.
Governs escape-hatch usage as technical debt: tracks each flag to a cause, prevents flag rot, drives descriptor/API fixes, and sets upgrade-time review policy across the org.
## Why escape hatches exist JPMS enforces **strong encapsulation** (only exported packages are visible; only opened packages allow deep reflection) and **reliable configuration** (only resolved modules are present, the graph is acyclic). During migration, real-world code and frameworks frequently need to break these rules *temporarily* — before every dependency has a correct `module-info`. The escape-hatch flags relax specific rules **without editing any module descriptor**, which is essential when you do not control the offending module. Each flag's target can be a specific module name, the special **`ALL-UNNAMED`** (all classpath code in the unnamed module), and for reads, **`ALL-SYSTEM`** etc. ## --add-exports <module>/<package>=<target> - **What it does:** makes the **public** types of a *non-exported* package readable by the target, at **compile and run** time. - **When:** you must call into a package a module does not `exports` — e.g. a JDK-internal `sun.*` package, or another module's implementation package. - **Limitation:** grants access to public members only; it does **not** enable reflection on private members. ## --add-opens <module>/<package>=<target> - **What it does:** **opens** a package to the target for **deep reflection** — `setAccessible(true)` on non-public fields/methods/constructors. - **When:** serialization libraries, dependency-injection containers, ORMs, mocking frameworks that reflect into private members of standard or third-party modules. Since Java 16/17 default-deny, these now fail loudly without it. - **Relation to exports:** opens is strictly about reflection; a package can be opened without being exported and vice versa. ## --add-modules <module> - **What it does:** adds the named module(s) to the **root set** so they are **resolved** even though no `requires` (or the default root computation) pulls them in. - **When:** - Modules **removed from the default module set** — e.g. the Java EE / CORBA modules (`java.xml.bind`, `java.activation`, `java.xml.ws`, …) deprecated in Java 9 and **removed in Java 11**; if you still need them as JDK modules (pre-removal) you had to add them. Post-removal you switch to standalone artifacts. - **Service providers** only discovered at runtime that you want present. - `--add-modules ALL-MODULE-PATH` to resolve every module on the path. ## --add-reads <source-module>=<target-module> - **What it does:** adds a **readability edge** so `source` can read `target` without declaring `requires`. - **When:** white-box testing (test code reading a module it shouldn't), or quickly wiring a dependency you can't yet add to `module-info`. Often paired with `--patch-module` in testing. ## Where the flags can live - Directly on `java`/`javac` command lines. - In environment variables **`JDK_JAVA_OPTIONS`** (java) / `JAVA_TOOL_OPTIONS`. - In the **jar manifest** as `Add-Opens:` / `Add-Exports:` entries, so a library can self-declare what it needs (e.g. an agent jar). - In an `@argfile`. ## Discipline: bridges, not architecture The danger is **flag rot**: escape hatches silently pile up in launch scripts and never get removed, so the app effectively runs with encapsulation disabled. Principal-level practice: - Track each flag to a **specific dependency and reason**; review them every upgrade. - Prefer the **durable fix**: add proper `exports`/`opens`/`requires` to descriptors you own, migrate off internal APIs (`jdeps --jdkinternals`), or adopt libraries that declare what they need in their manifest. - Centralize and document them rather than scattering across scripts. ## Key takeaways - **exports** = use a package's public API; **opens** = deep reflection; **add-modules** = force a module to resolve; **add-reads** = add a requires edge ad hoc. - `ALL-UNNAMED` targets classpath code. - All four are migration **bridges**; the endpoint is correct descriptors and supported APIs.
- You upgraded to Java 11 and a library that used JAXB now throws ClassNotFoundException for javax.xml.bind. What happened and how do you fix it?The Java EE modules (java.xml.bind, java.activation, etc.) were deprecated in Java 9 and removed from the JDK in Java 11, so they are no longer resolvable. The durable fix is to add the standalone Jakarta JAXB API + implementation jars as normal dependencies; --add-modules no longer helps because the JDK module is gone.
- Why might --add-exports be insufficient even though it grants access to a package?--add-exports only exposes public types. If the code reflects into private fields/methods (setAccessible), you also need --add-opens, which grants deep reflective access; exporting alone does not permit reflection on non-public members.
saying these in an interview costs you the question
- Treating --add-exports and --add-opens as interchangeable
- Thinking --add-modules can resurrect java.xml.bind in Java 11+ (it was removed, not just unresolved)
- Leaving escape-hatch flags in launch scripts indefinitely without review
- Believing manifest Add-Opens is unavailable and flags must always be CLI
- Forgetting ALL-UNNAMED is needed to reach classpath code