{"record":{"id":"81715cc2faf62427","repo":"elastic/elasticsearch","slug":"jar-hell-duplicate-jar-on-classpath-path","errorCode":null,"errorMessage":"jar hell!\nduplicate jar on classpath: {path}","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":219,"sourceCode":"    @SuppressForbidden(reason = \"needs JarFile for speed, just reading entries\")\n    public static void checkJarHell(Set<URL> urls, Consumer<String> output) throws IOException {\n        // we don't try to be sneaky and use deprecated/internal/not portable stuff\n        // like sun.boot.class.path, and with jigsaw we don't yet have a way to get\n        // a \"list\" at all. So just exclude any elements underneath the java home\n        String javaHome = System.getProperty(\"java.home\");\n        output.accept(\"java.home: \" + javaHome);\n        final Map<String, Path> clazzes = new HashMap<>(32768);\n        Set<Path> seenJars = new HashSet<>();\n        for (final URL url : urls) {\n            final Path path = toPath(url);\n            // exclude system resources\n            if (path.startsWith(javaHome)) {\n                output.accept(\"excluding system resource: \" + path);\n                continue;\n            }\n            if (path.toString().endsWith(\".jar\")) {\n                if (seenJars.add(path) == false) {\n                    throw new IllegalStateException(\"jar hell!\" + System.lineSeparator() + \"duplicate jar on classpath: \" + path);\n                }\n                output.accept(\"examining jar: \" + path);\n                try (JarFile file = new JarFile(path.toString())) {\n                    Manifest manifest = file.getManifest();\n                    if (manifest != null) {\n                        checkManifest(manifest, path);\n                    }\n                    // inspect entries\n                    Enumeration<JarEntry> elements = file.entries();\n                    while (elements.hasMoreElements()) {\n                        String entry = elements.nextElement().getName();\n                        if (entry.endsWith(\".class\")) {\n                            // for jar format, the separator is defined as /\n                            entry = entry.replace('/', '.').substring(0, entry.length() - 6);\n                            checkClass(clazzes, entry, path);\n                        }\n                    }\n                }","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/libs/core/src/main/java/org/elasticsearch/jdk/JarHell.java#L201-L237","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Ensure the merged URL set (modules + classpath) has no duplicate jars\nboolean noDuplicateJars(Collection<URL> urls) throws IOException {\n    Set<Path> seen = new HashSet<>();\n    for (URL u : urls) {\n        if (u.toString().endsWith(\".jar\") && !seen.add(Path.of(u.toURI()))) return false;\n    }\n    return true;\n}","typeGuard":"static boolean mergedUrlsHaveNoDuplicateJars(Collection<URL> urls) {\n    try {\n        Set<Path> seen = new HashSet<>();\n        for (URL u : urls) {\n            if (u.toString().endsWith(\".jar\") && !seen.add(Path.of(u.toURI()))) return false;\n        }\n        return true;\n    } catch (Exception e) { return false; }\n}","tryCatchPattern":"// IllegalStateException is fatal at startup; fix the classpath/module layout\n// rather than catching at runtime. Identify the duplicate via JarHell's debug output.","preventionTips":["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."],"tags":["classpath","startup","jar-hell","duplicate","modules","elasticsearch-core"],"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T11:17:21.771Z"}