elastic/elasticsearch · error · IllegalStateException
jar hell! duplicate jar on classpath: {path}
Error message
jar hell!
duplicate jar on classpath: {path} What it means
Thrown as an IllegalStateException by JarHell.checkJarHell(Set<URL>, Consumer) during the deep duplicate scan when the same jar Path (after URL-to-Path conversion) is encountered twice across the merged classpath-and-modules URL set. Unlike error 478 (which catches text-level duplicates during string parsing), this catches path-level duplicates that survive URL resolution — typically when a jar enters the set through two different sources (e.g. once via the classpath and once via a module URL).
Source
Thrown at libs/core/src/main/java/org/elasticsearch/jdk/JarHell.java:219
@SuppressForbidden(reason = "needs JarFile for speed, just reading entries")
public static void checkJarHell(Set<URL> urls, Consumer<String> output) throws IOException {
// we don't try to be sneaky and use deprecated/internal/not portable stuff
// like sun.boot.class.path, and with jigsaw we don't yet have a way to get
// a "list" at all. So just exclude any elements underneath the java home
String javaHome = System.getProperty("java.home");
output.accept("java.home: " + javaHome);
final Map<String, Path> clazzes = new HashMap<>(32768);
Set<Path> seenJars = new HashSet<>();
for (final URL url : urls) {
final Path path = toPath(url);
// exclude system resources
if (path.startsWith(javaHome)) {
output.accept("excluding system resource: " + path);
continue;
}
if (path.toString().endsWith(".jar")) {
if (seenJars.add(path) == false) {
throw new IllegalStateException("jar hell!" + System.lineSeparator() + "duplicate jar on classpath: " + path);
}
output.accept("examining jar: " + path);
try (JarFile file = new JarFile(path.toString())) {
Manifest manifest = file.getManifest();
if (manifest != null) {
checkManifest(manifest, path);
}
// inspect entries
Enumeration<JarEntry> elements = file.entries();
while (elements.hasMoreElements()) {
String entry = elements.nextElement().getName();
if (entry.endsWith(".class")) {
// for jar format, the separator is defined as /
entry = entry.replace('/', '.').substring(0, entry.length() - 6);
checkClass(clazzes, entry, path);
}
}
}View on GitHub (pinned to db6a809a66)
Solutions
- Identify which two URL sources contribute the duplicate jar (enable JarHell debug output via the Consumer).
- Remove the jar from one of the two locations — either the classpath entry or the module path entry.
- If the duplicate comes from a plugin, ensure the plugin does not bundle a jar already present in ES_HOME/lib.
- Rebuild the distribution/module layer so each jar appears in exactly one source set.
Defensive patterns
Strategy: validation
Validate before calling
// Ensure the merged URL set (modules + classpath) has no duplicate jars
boolean noDuplicateJars(Collection<URL> urls) throws IOException {
Set<Path> seen = new HashSet<>();
for (URL u : urls) {
if (u.toString().endsWith(".jar") && !seen.add(Path.of(u.toURI()))) return false;
}
return true;
} Type guard
static boolean mergedUrlsHaveNoDuplicateJars(Collection<URL> urls) {
try {
Set<Path> seen = new HashSet<>();
for (URL u : urls) {
if (u.toString().endsWith(".jar") && !seen.add(Path.of(u.toURI()))) return false;
}
return true;
} catch (Exception e) { return false; }
} Try / catch
// IllegalStateException is fatal at startup; fix the classpath/module layout // rather than catching at runtime. Identify the duplicate via JarHell's debug output.
Prevention
- Keep each jar in exactly one source set (classpath OR module path, not both).
- Plugins must not bundle jars already present in ES_HOME/lib.
- Validate the merged URL set with JarHell.checkJarHell in tests.
- Inspect distribution packaging for accidental double-placement of libraries.
When it happens
Trigger: Calling JarHell.checkJarHell(urls, output) where the URL set (from parseModulesAndClassPath) contains the same jar path twice — e.g. a jar is both on the classpath and registered as a non-JDK boot module location. The seenJars.add fails at line 218.
Common situations: A custom module layer that re-exports a jar already on the classpath. Plugin modules that duplicate a base lib. A distribution packaging bug placing the same jar in both lib/ and a module path. Startup wiring that merges classpath and module URLs without dedup.
Related errors
- jar hell! duplicate jar [{element}] on classpath: {classPath
- Classpath should not contain empty elements! (outdated shell
- unknown scheme:{rootURI.getScheme()}
- jar hell! class: {clazz} exists multiple times in jar: {jarp
- jar hell! class: {clazz} jar1: {previous} jar2: {jarpath}
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/81715cc2faf62427.
Report an issue: GitHub.