🔴 The Error You're Seeing
Confirm this matches your console output. If it does, you're in the right place.
// DOC-DERIVED — canonical real-world shape (log4j/slf4j-log4j12 automatic-module clash); identical failure reported across Dropwizard, JavaFX and Forge stacks with Resolver.checkExportSuppliers frames:
Exception in thread "main" java.lang.module.ResolutionException: Modules slf4j.log4j12 and log4j export package org.apache.log4j to module kubernetes.model.common
at java.base/java.lang.module.Resolver.resolveFail(Resolver.java:901)
at java.base/java.lang.module.Resolver.failTwoSuppliers(Resolver.java:815)
at java.base/java.lang.module.Resolver.checkExportSuppliers(Resolver.java:736)
at java.base/java.lang.module.Resolver.finish(Resolver.java:380)
at java.base/java.lang.module.Configuration.resolveAndBind(Configuration.java:494)⚡ Quick Fix Works 80% of the time
Remove one of the two supplying jars from the module path, or drop the requires edge that makes your module read both — exactly one supplier per package per consumer.
# which jars declare/derive the conflicting package?
jar --describe-module --file libs/slf4j-log4j12.jar | grep -A5 "exports"
jar --describe-module --file libs/log4j.jar | grep -A5 "exports"
# then remove one jar from -p / mods dir and rebuild🧠 Why this Happens
Tap to expand the deep technical explanation
During Configuration.resolveAndBind the Resolver runs checkExportSuppliers: for every package, every module that READS it must see exactly one supplier. The trigger is therefore a READER, not mere presence — an application requiring (or implicitly reading, in the case of automatic modules which read everything) two jars that both export the same package aborts resolution here, before any layer exists. This is why the log4j family hits it constantly: slf4j-log4j12 ships classes inside org.apache.log4j while the log4j automatic module exports the same package, and any modular app near them reads both. Note the sibling behavior: two EXPORTERS with no common reader pass resolution and instead die at layer creation with LayerInstantiationException.
The HITEC City Parking Spot Analogy:
Two textbook publishers both claim to supply Chapter 5 to your syllabus. The registrar refuses to process enrollment until the reading list names exactly one source chapter per course.
🔁 How to Reproduce Confirm this is your error
Place two plain jars (no module-info) that both contain/export the same package onto --module-path next to any explicit modular app, and force them into the graph with --add-modules. Automatic modules read every module, so checkExportSuppliers sees two suppliers and throws this exception at startup — no compile step warns you. DOC-DERIVED — captured shapes vary only in module names.
🛠️ Solutions (5 Ways to Fix)
Eliminate one exporter from the resolved graph
👉 Use this when the duplicate is a leftover artifact or an optional feature you can drop.
Delete the jar from mods/, remove its requires line from consumers, and rerun. Resolution is all-or-nothing: one fewer supplier instantly satisfies the uniqueness rule.
rm mods/slf4j-log4j12-1.7.26.jar
java --module-path mods -m devtoolpick.app/com.devinhyderabad.app.MainRestrict exports with ...to clauses so readers stop colliding
👉 Use this when both modules genuinely must exist but their audiences differ.
checkExportSuppliers compares the TARGETS of each export. Qualified exports limit visibility to named consumers; disjoint target sets mean no single reader sees two suppliers, so resolution proceeds.
// core
module devtoolpick.core {
exports com.devinhyderabad.model to devtoolpick.app;
}
// legacy
module devtoolpick.legacy {
exports com.devinhyderabad.model to devtoolpick.internaltools;
}Merge the two modules into one
👉 Use this when the overlap reflects an unfinished refactor rather than separate products.
Combining descriptors removes the conflict structurally: one name, one exports list. Often the fastest exit when legacy was scheduled for absorption anyway.
module devtoolpick.core {
exports com.devinhyderabad.model;
exports com.devinhyderabad.legacybridge;
// absorbed former devtoolpick.legacy contents
}Move the non-modular party back to the classpath
👉 Use this when one side is a plain jar without module-info that landed on the module path by accident.
Automatic modules export everything and read everything, manufacturing supplier conflicts. Keeping plain jars on --class-path exempts them from inter-module export checks entirely while your real modules stay clean.
java --class-path libs/legacy-model.jar \
--module-path mods \
-m devtoolpick.app/com.devinhyderabad.app.MainTrace the graph with jdeps and --show-module-resolution
👉 Use this when several candidates export overlapping packages and you need the full map fast.
--show-module-resolution prints each resolved edge as the JVM works, while jdeps --check summarizes exports per module — together they pinpoint both offending suppliers without trial and error.
java --show-module-resolution --module-path mods -m devtoolpick.app/... 2>&1 | head -40
jdeps --module-path mods --check devtoolpick.core📋 Version Notes
No module resolution; duplicates legal on classpath.
Resolver checks active since 9; log4j-family automatic modules the classic offenders.
Same; plain-launch splits surface as LayerInstantiationException instead (see sibling).
Unchanged; programmatic configurations fail identically.
🛡️ How to Prevent This Next Time
Resolve the module graph in CI (jdeps --check on every artifact), keep third-party non-modular jars off the module path unless converted, and document a single-owner policy per exported package in the repo README.