{"record":{"id":"ca7ab6a7ca8b4609","repo":"elastic/elasticsearch","slug":"classpath-should-not-contain-empty-elements-outd","errorCode":null,"errorMessage":"Classpath should not contain empty elements! (outdated shell script from a previous version?) classpath='{classPath}'","messagePattern":"Classpath should not contain empty elements! \\(outdated shell script from a previous version\\?\\) classpath='(.+?)'","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"libs/core/src/main/java/org/elasticsearch/jdk/JarHell.java","lineNumber":125,"sourceCode":"        }\n        String pathSeparator = System.getProperty(\"path.separator\");\n        String fileSeparator = System.getProperty(\"file.separator\");\n        String elements[] = classPath.split(pathSeparator);\n        Set<URL> urlElements = new LinkedHashSet<>(); // order is already lost, but some filesystems have it\n        for (String element : elements) {\n            /*\n             * Technically empty classpath element behaves like CWD.\n             * So below is the \"correct\" code, however in practice with ES, this is usually just a misconfiguration,\n             * from old shell scripts left behind or something:\n             *\n             *   if (element.isEmpty()) {\n             *      element = System.getProperty(\"user.dir\");\n             *   }\n             *\n             * Instead we just throw an exception, and keep it clean.\n             */\n            if (element.isEmpty()) {\n                throw new IllegalStateException(\n                    \"Classpath should not contain empty elements! (outdated shell script from a previous\"\n                        + \" version?) classpath='\"\n                        + classPath\n                        + \"'\"\n                );\n            }\n            // we should be able to just Paths.get() each element, but unfortunately this is not the\n            // whole story on how classpath parsing works: if you want to know, start at sun.misc.Launcher,\n            // be sure to stop before you tear out your eyes. we just handle the \"alternative\" filename\n            // specification which java seems to allow, explicitly, right here...\n            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                }","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/libs/core/src/main/java/org/elasticsearch/jdk/JarHell.java#L107-L143","documentation":"Thrown as an IllegalStateException by JarHell.parseClassPath when the java.class.path system property, split on the path separator, yields one or more empty elements (e.g. consecutive separators like 'a.jar::b.jar' or a leading/trailing separator). Empty classpath elements technically behave like CWD, but Elasticsearch treats this as a misconfiguration — usually stale shell scripts from a prior version — and refuses to start. The full offending classpath string is included.","triggerScenarios":"Setting ES_CLASSPATH or java.class.path with a duplicated separator (`lib/*::plugins/*`), a trailing colon, or a leading colon. Passing `-cp :foo.jar` or `-cp foo.jar:`. An env-var built by concatenating entries with an extra separator. An old bin/elasticsearch shell script from a previous ES version.","commonSituations":"Upgrading Elasticsearch and leaving old shell wrappers that append an empty classpath element. A shell variable that was empty and got concatenated into the classpath. A packaging script that joins paths with `${VAR}:` unconditionally. Custom plugin startup scripts.","solutions":["Inspect the classpath string echoed in the message; remove the duplicated/leading/trailing separator.","If building the classpath from variables, filter out empty entries before joining: `printf '%s:' \"${arr[@]}\" | sed 's/:$//'`.","Replace stale bin/* shell scripts with the version shipping in the current distribution.","Check ES_JAVA_OPTS / ES_CLASSPATH in the environment for stray separators."],"exampleFix":"# before\nES_CLASSPATH=\"lib/*::plugins/*\"   # double colon\n\n# after\nES_CLASSPATH=\"lib/*:plugins/*\"","handlingStrategy":"validation","validationCode":"// De-duplicate and strip empty classpath elements before JVM startup\nString cleanClasspath(String cp) {\n    String sep = System.getProperty(\"path.separator\");\n    return Arrays.stream(cp.split(Pattern.quote(sep)))\n        .filter(s -> !s.isEmpty())\n        .distinct()\n        .collect(Collectors.joining(sep));\n}","typeGuard":"static boolean classpathHasNoEmptyElements(String cp) {\n    String sep = System.getProperty(\"path.separator\");\n    return Arrays.stream(cp.split(Pattern.quote(sep), -1)).noneMatch(String::isEmpty);\n}","tryCatchPattern":"// IllegalStateException is thrown at startup; the fix is configuration, not catch.\n// Validate the classpath string before passing it to the JVM.","preventionTips":["Build classpath strings by joining non-empty entries with a single separator.","When concatenating optional variables, skip them when empty.","Replace stale bin/* shell scripts when upgrading Elasticsearch.","Run `echo $ES_CLASSPATH | tr ':' '\\n' | grep -q '^$'` as a preflight check."],"tags":["classpath","startup","jar-hell","shell","configuration","elasticsearch-core"],"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T08:17:17.861Z"}