🔴 The Error You're Seeing
Confirm this matches your console output. If it does, you're in the right place.
// CAPTURED — OpenJDK 25.0.2 (Temurin), macOS arm64. Two modules export com.devinhyderabad.model; legacy forced into resolution via --add-modules.
Error occurred during initialization of boot layer
java.lang.LayerInstantiationException: Package com.devinhyderabad.model in both module devtoolpick.legacy and module devtoolpick.core⚡ Quick Fix Works 80% of the time
Remove or repackage one of the two exporters so each package has exactly one owner, then rebuild the module path.
# who exports what?
jar --describe-module --file out/core.jar | grep -A20 "exports"
jar --describe-module --file out/legacy.jar | grep -A20 "exports"
jdeps --print-module-deps --multi-release 21 lib/*.jar🧠 Why this Happens
Tap to expand the deep technical explanation
After Configuration.resolve() picks the module graph, actually DEFINING the layer walks every resolved module’s packages; two distinct owners for one exported package would make type identity ambiguous for consumers, so ModuleLayer creation throws LayerInstantiationException before any application code runs. The check is graph-wide: in the capture, nothing required devtoolpick.legacy, so a plain launch booted happily with the jar parked unused on --module-path — only forcing it into resolution via --add-modules exposed the collision. javac catches the same conflict earlier when your own module requires both exporters directly ("reads package X from both").
The HITEC City Parking Spot Analogy:
Two couriers both list apartment 4B as theirs on the building manifest. The building manager refuses to register EITHER delivery route until the paperwork shows one responsible courier per address.
🔁 How to Reproduce Confirm this is your error
Build core and legacy jars whose module-info both export com.devinhyderabad.model, plus an app requiring only core. Plain launch boots fine (legacy stays unresolved). Re-run with --add-modules devtoolpick.legacy to force both into the graph: boot-layer initialization fails with the captured message. (Lab capture: OpenJDK 25.0.2.)
🛠️ Solutions (5 Ways to Fix)
Give every package exactly one owning module
👉 Use this when you control both artifacts and can merge or move code properly.
Either fold the overlapping classes into the canonical module and delete them from the legacy one, or rename one package (com.devinhyderabad.model.legacy). Ownership clarity is what the resolver demands and what humans need anyway.
// devtoolpick.core/module-info.java - sole owner after cleanup
module devtoolpick.core {
exports com.devinhyderabad.model;
}
// devtoolpick.legacy/module-info.java - renamed package
module devtoolpick.legacy {
requires transitive devtoolpick.core;
exports com.devinhyderabad.model.legacy;
}Drop the duplicate artifact from the module path
👉 Use this when the second copy is a stale dependency nobody actually needs.
Frequently the clash is an old version riding alongside the new one in mods/ or the -p list. Delete it and let the remaining module satisfy all requires clauses — remember that merely removing it from --add-modules also hides the failure without fixing ownership.
ls mods/
# devtoolpick-core-2.0.jar devtoolpick-legacy-1.0.jar <- old core!
rm mods/devtoolpick-core-1.0.jar
java --module-path mods -m devtoolpick.app/com.devinhyderabad.app.MainStop automatic modules from exporting your packages twice
👉 Use this when mixing explicit module-info jars with plain library jars on the module path.
An automatic module derives its name from the jar filename and exports everything — including packages your real module also exports. Convert key libraries to explicit modules or push non-modular ones back to the classpath where split rules do not apply across the two worlds.
# classpath world + modular app coexist without split checks between them
java --class-path libs/legacy-model.jar \
--module-path mods \
-m devtoolpick.app/com.devinhyderabad.app.MainRun jdeps over the whole graph before launching
👉 Use this as the pre-flight check in build scripts for any modular deployment.
jdeps reports package-level dependencies and flags split packages across the analyzed set, catching at build time what LayerInstantiationException otherwise announces in production start scripts.
jdeps --multi-release 21 --module-path mods --check devtoolpick.core
jdeps -s lib/*.jar # summary of offender edgesKeep the offending jar on the classpath instead of the module path
👉 Use this during migration when one legacy jar cannot become modular yet.
Classpath jars live outside the module graph entirely: no exports are declared, so they cannot collide with a module package at layer definition. This is a supported coexistence mode while the migration debt burns down — track the offending artifact explicitly so it does not quietly become permanent architecture.
java --module-path mods \
--class-path libs/legacy-model-1.0.jar \
-m devtoolpick.app/com.devinhyderabad.app.Main📋 Version Notes
No layers; duplicate packages resolved silently by classpath order.
LayerInstantiationException present from 9; automatic-module pitfalls common.
Identical behavior; tooling (jdeps/jlink) mature.
Unchanged; multi-release jars still count as single owners.
🛡️ How to Prevent This Next Time
Enforce one-module-per-package with jdeps in CI, convert shared libraries to explicit modules early, and forbid raw jar drops onto the module path — every artifact enters through the build with a declared name.