elastic/elasticsearch · critical · IllegalStateException

jar hell! class: jar1: jar2

Error message

jar hell!
class: {clazz}
jar1: {previous}
jar2: {jarpath}

What it means

Thrown by JarHell.checkClass when the same fully-qualified class name is present in two DIFFERENT jars on the classpath (previous jar != current jarpath). This is the classic 'jar hell' condition: the JVM could load either copy, so Elasticsearch refuses to start. The message lists both conflicting jar paths.

Solutions

  1. Identify which two jars from the message ship the named class and remove or exclude one of them.
  2. Run 'jar tf <jar> | grep <clazz>' on both jars to confirm the duplication.
  3. Use Gradle's dependencyInsight or 'gradle dependencies' to find the conflicting transitive pull and add an exclude rule.
  4. If it is a plugin, rebuild it without bundling the duplicate library (mark the dep as 'provided' or 'compileOnly').

Example fix

// before: both lib/foo-1.0.jar and modules/bar/foo-1.1.jar contain com.foo.Bar

// after: exclude the transitive duplicate in build.gradle
dependencies {
  implementation('com.example:bar') {
    exclude group: 'com.foo', module: 'foo'
  }
}
Defensive patterns

Strategy: validation

Validate before calling

// Before launch, scan the full classpath for cross-jar class collisions
import java.util.jar.*;
import java.util.*;
import java.nio.file.*;

void assertNoCrossJarDuplicates(List<Path> jars) throws IOException {
  Map<String, Path> owners = new HashMap<>();
  for (Path jar : jars) {
    try (var jf = new JarFile(jar.toFile())) {
      var e = jf.entries();
      while (e.hasMoreElements()) {
        String n = e.nextElement().getName();
        if (!n.endsWith(".class")) continue;
        Path prev = owners.put(n, jar);
        if (prev != null && !prev.equals(jar)) {
          throw new IllegalStateException(n + " in both " + prev + " and " + jar);
        }
      }
    }
  }
}

Prevention

When it happens

Trigger: Two distinct jars on the classpath each contain a class with the identical fully-qualified name. checkClass() records the first jar per class name; when the same class name is encountered in a second jar, previous.equals(jarpath) is false so this else-branch fires, naming jar1 and jar2.

Common situations: A transitive dependency pulled in two versions of the same library; a plugin bundles a library already shipped by ES core; a shaded jar conflicts with the original; copy-pasting a dependency jar into lib/ that already lives in modules/.

Related errors


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

Appendix: source

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

        }
        Path previous = clazzes.put(clazz, jarpath);
        if (previous != null) {
            if (previous.equals(jarpath)) {
                // throw a better exception in this ridiculous case.
                // unfortunately the zip file format allows this buggy possibility
                // UweSays: It can, but should be considered as bug :-)
                throw new IllegalStateException(
                    "jar hell!"
                        + System.lineSeparator()
                        + "class: "
                        + clazz
                        + System.lineSeparator()
                        + "exists multiple times in jar: "
                        + jarpath
                        + " !!!!!!!!!"
                );
            } else {
                throw new IllegalStateException(
                    "jar hell!"
                        + System.lineSeparator()
                        + "class: "
                        + clazz
                        + System.lineSeparator()
                        + "jar1: "
                        + previous
                        + System.lineSeparator()
                        + "jar2: "
                        + jarpath
                );
            }
        }
    }

    private static URL toURL(URI uri) {
        try {
            return uri.toURL();

View on GitHub (pinned to db6a809a66)