{"record":{"id":"40ffd6b78c612b69","repo":"elastic/elasticsearch","slug":"jar-hell-duplicate-jar-element-on-classpath","errorCode":null,"errorMessage":"jar hell!\nduplicate jar [{element}] on classpath: {classPath}","messagePattern":"jar hell!\nduplicate jar \\[(.+?)\\] on classpath: (.+?)","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"libs/core/src/main/java/org/elasticsearch/jdk/JarHell.java","lineNumber":154,"sourceCode":"            if (element.startsWith(\"/\") && \"\\\\\".equals(fileSeparator)) {\n                // \"correct\" the entry to become a normal entry\n                // change to correct file separators\n                element = element.replace(\"/\", \"\\\\\");\n                // if there is a drive letter, nuke the leading separator\n                if (element.length() >= 3 && element.charAt(2) == ':') {\n                    element = element.substring(1);\n                }\n            }\n            // now just parse as ordinary file\n            try {\n                if (element.equals(\"/\")) {\n                    // Eclipse adds this to the classpath when running unit tests...\n                    continue;\n                }\n                URL url = PathUtils.get(element).toUri().toURL();\n                // junit4.childvm.count\n                if (urlElements.add(url) == false && element.endsWith(\".jar\")) {\n                    throw new IllegalStateException(\n                        \"jar hell!\" + System.lineSeparator() + \"duplicate jar [\" + element + \"] on classpath: \" + classPath\n                    );\n                }\n            } catch (MalformedURLException e) {\n                // should not happen, as we use the filesystem API\n                throw new RuntimeException(e);\n            }\n        }\n        return Collections.unmodifiableSet(urlElements);\n    }\n\n    /**\n     * Returns a set of URLs that contain artifacts from both the non-JDK boot\n     * modules and class path. These URLs constitute the loadable application\n     * artifacts in the system class loader.\n     */\n    public static Set<URL> parseModulesAndClassPath() {\n        return Stream.concat(parseClassPath().stream(), JarHell.nonJDKBootModuleURLs()).collect(toUnmodifiableSet());","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/libs/core/src/main/java/org/elasticsearch/jdk/JarHell.java#L136-L172","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["De-duplicate the classpath: ensure each jar appears once (use `readlink -f` / `realpath` to canonicalize before comparing).","If the duplicate is a symlink-vs-real conflict, list only the canonical path.","Audit the bin/* script or wrapper that builds ES_CLASSPATH for double-appends.","Run `echo $ES_CLASSPATH | tr ':' '\\n' | sort | uniq -d` to find the duplicate."],"exampleFix":"# before\nES_CLASSPATH=\"/opt/es/lib/x.jar:/opt/es/lib/x.jar\"\n\n# after\nES_CLASSPATH=\"/opt/es/lib/x.jar\"","handlingStrategy":"validation","validationCode":"// Canonicalize and de-duplicate classpath entries before startup\nString dedupClasspath(String cp) throws IOException {\n    String sep = System.getProperty(\"path.separator\");\n    LinkedHashSet<String> seen = new LinkedHashSet<>();\n    for (String e : cp.split(Pattern.quote(sep))) {\n        if (e.isEmpty()) continue;\n        seen.add(Path.of(e).toRealPath().toString()); // canonicalize symlinks\n    }\n    return String.join(sep, seen);\n}","typeGuard":"static boolean classpathHasNoDuplicateJars(String cp) throws IOException {\n    String sep = System.getProperty(\"path.separator\");\n    Set<Path> seen = new HashSet<>();\n    for (String e : cp.split(Pattern.quote(sep))) {\n        if (e.endsWith(\".jar\") && !seen.add(Path.of(e).toRealPath())) return false;\n    }\n    return true;\n}","tryCatchPattern":"// IllegalStateException surfaces at startup; dedupe the classpath before launch\n// rather than catching at runtime.","preventionTips":["Canonicalize paths (realpath/readlink -f) before comparing jar entries.","Don't append a jar that's already in ES_HOME/lib from plugin scripts.","Audit IDE run configs and custom wrappers for duplicate entries.","Use `echo $ES_CLASSPATH | tr ':' '\\n' | sort | uniq -d` as a preflight check."],"tags":["classpath","startup","jar-hell","duplicate","elasticsearch-core"],"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T08:17:17.861Z"}