elastic/elasticsearch · error · IllegalStateException

Classpath should not contain empty elements! (outdated shell

Error message

Classpath should not contain empty elements! (outdated shell script from a previous version?) classpath='{classPath}'

What it means

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.

Source

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

        }
        String pathSeparator = System.getProperty("path.separator");
        String fileSeparator = System.getProperty("file.separator");
        String elements[] = classPath.split(pathSeparator);
        Set<URL> urlElements = new LinkedHashSet<>(); // order is already lost, but some filesystems have it
        for (String element : elements) {
            /*
             * Technically empty classpath element behaves like CWD.
             * So below is the "correct" code, however in practice with ES, this is usually just a misconfiguration,
             * from old shell scripts left behind or something:
             *
             *   if (element.isEmpty()) {
             *      element = System.getProperty("user.dir");
             *   }
             *
             * Instead we just throw an exception, and keep it clean.
             */
            if (element.isEmpty()) {
                throw new IllegalStateException(
                    "Classpath should not contain empty elements! (outdated shell script from a previous"
                        + " version?) classpath='"
                        + classPath
                        + "'"
                );
            }
            // we should be able to just Paths.get() each element, but unfortunately this is not the
            // whole story on how classpath parsing works: if you want to know, start at sun.misc.Launcher,
            // be sure to stop before you tear out your eyes. we just handle the "alternative" filename
            // specification which java seems to allow, explicitly, right here...
            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);
                }

View on GitHub (pinned to db6a809a66)

Solutions

  1. Inspect the classpath string echoed in the message; remove the duplicated/leading/trailing separator.
  2. If building the classpath from variables, filter out empty entries before joining: `printf '%s:' "${arr[@]}" | sed 's/:$//'`.
  3. Replace stale bin/* shell scripts with the version shipping in the current distribution.
  4. Check ES_JAVA_OPTS / ES_CLASSPATH in the environment for stray separators.

Example fix

# before
ES_CLASSPATH="lib/*::plugins/*"   # double colon

# after
ES_CLASSPATH="lib/*:plugins/*"
Defensive patterns

Strategy: validation

Validate before calling

// De-duplicate and strip empty classpath elements before JVM startup
String cleanClasspath(String cp) {
    String sep = System.getProperty("path.separator");
    return Arrays.stream(cp.split(Pattern.quote(sep)))
        .filter(s -> !s.isEmpty())
        .distinct()
        .collect(Collectors.joining(sep));
}

Type guard

static boolean classpathHasNoEmptyElements(String cp) {
    String sep = System.getProperty("path.separator");
    return Arrays.stream(cp.split(Pattern.quote(sep), -1)).noneMatch(String::isEmpty);
}

Try / catch

// IllegalStateException is thrown at startup; the fix is configuration, not catch.
// Validate the classpath string before passing it to the JVM.

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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