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

  1. Identify which two URL sources contribute the duplicate jar (enable JarHell debug output via the Consumer).
  2. Remove the jar from one of the two locations — either the classpath entry or the module path entry.
  3. If the duplicate comes from a plugin, ensure the plugin does not bundle a jar already present in ES_HOME/lib.
  4. 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

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


AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12). Data as JSON: /api/errors/81715cc2faf62427. Report an issue: GitHub.