elastic/elasticsearch · error · IllegalStateException

jar hell! duplicate jar [{element}] on classpath: {classPath

Error message

jar hell!
duplicate jar [{element}] on classpath: {classPath}

What it means

Thrown as an IllegalStateException by JarHell.parseClassPath when a jar element (a path ending in .jar) resolves to a URL already seen earlier in the same classpath parse. Two different classpath strings that point to the same underlying jar file (after URL normalization) trip this check; the offending element and the full classpath are included. This is the classpath-parsing-level duplicate detection, distinct from the deeper duplicate-class scan in checkJarHell.

Source

Thrown at libs/core/src/main/java/org/elasticsearch/jdk/JarHell.java:154

            if (element.startsWith("/") && "\\".equals(fileSeparator)) {
                // "correct" the entry to become a normal entry
                // change to correct file separators
                element = element.replace("/", "\\");
                // if there is a drive letter, nuke the leading separator
                if (element.length() >= 3 && element.charAt(2) == ':') {
                    element = element.substring(1);
                }
            }
            // now just parse as ordinary file
            try {
                if (element.equals("/")) {
                    // Eclipse adds this to the classpath when running unit tests...
                    continue;
                }
                URL url = PathUtils.get(element).toUri().toURL();
                // junit4.childvm.count
                if (urlElements.add(url) == false && element.endsWith(".jar")) {
                    throw new IllegalStateException(
                        "jar hell!" + System.lineSeparator() + "duplicate jar [" + element + "] on classpath: " + classPath
                    );
                }
            } catch (MalformedURLException e) {
                // should not happen, as we use the filesystem API
                throw new RuntimeException(e);
            }
        }
        return Collections.unmodifiableSet(urlElements);
    }

    /**
     * Returns a set of URLs that contain artifacts from both the non-JDK boot
     * modules and class path. These URLs constitute the loadable application
     * artifacts in the system class loader.
     */
    public static Set<URL> parseModulesAndClassPath() {
        return Stream.concat(parseClassPath().stream(), JarHell.nonJDKBootModuleURLs()).collect(toUnmodifiableSet());

View on GitHub (pinned to db6a809a66)

Solutions

  1. De-duplicate the classpath: ensure each jar appears once (use `readlink -f` / `realpath` to canonicalize before comparing).
  2. If the duplicate is a symlink-vs-real conflict, list only the canonical path.
  3. Audit the bin/* script or wrapper that builds ES_CLASSPATH for double-appends.
  4. Run `echo $ES_CLASSPATH | tr ':' '\n' | sort | uniq -d` to find the duplicate.

Example fix

# before
ES_CLASSPATH="/opt/es/lib/x.jar:/opt/es/lib/x.jar"

# after
ES_CLASSPATH="/opt/es/lib/x.jar"
Defensive patterns

Strategy: validation

Validate before calling

// Canonicalize and de-duplicate classpath entries before startup
String dedupClasspath(String cp) throws IOException {
    String sep = System.getProperty("path.separator");
    LinkedHashSet<String> seen = new LinkedHashSet<>();
    for (String e : cp.split(Pattern.quote(sep))) {
        if (e.isEmpty()) continue;
        seen.add(Path.of(e).toRealPath().toString()); // canonicalize symlinks
    }
    return String.join(sep, seen);
}

Type guard

static boolean classpathHasNoDuplicateJars(String cp) throws IOException {
    String sep = System.getProperty("path.separator");
    Set<Path> seen = new HashSet<>();
    for (String e : cp.split(Pattern.quote(sep))) {
        if (e.endsWith(".jar") && !seen.add(Path.of(e).toRealPath())) return false;
    }
    return true;
}

Try / catch

// IllegalStateException surfaces at startup; dedupe the classpath before launch
// rather than catching at runtime.

Prevention

When it happens

Trigger: Listing the same jar twice in java.class.path via different path spellings that normalize to the same URL: e.g. '/opt/es/lib/x.jar:/opt/es/lib/x.jar', or a symlink plus its real path, or './x.jar' alongside an absolute form. Triggered at line 153 when urlElements.add returns false and the element ends with .jar.

Common situations: A plugin install script appending a jar that's already on the base classpath. Custom launch wrappers concatenating ES_HOME/lib/* with an explicit jar list. Symlinks causing the same physical jar to appear under two paths. A misconfigured IDE run config listing a dependency twice.

Related errors


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