elastic/elasticsearch · error · IllegalStateException

Expected a single original jar, but found: ${oldJarNames}

Error message

Expected a single original jar, but found: ${oldJarNames}

What it means

Thrown as IllegalStateException at the start of JarApiComparisonTask.compare() when the oldJar FileCollection resolves to more than one file. The API comparison logic compares exactly one old (reference) jar against one new jar; multiple old jars make the comparison ambiguous and indicate a configuration error in how the old jar dependency was declared.

Source

Thrown at build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/JarApiComparisonTask.java:72

 *     a non-exported package considered part of the stable api?</li>
 *     <li>Changing method types to their superclass or return types to an implementation
 *     class will be considered a change by this approach, even though that doesn't break
 *     an API.</li>
 *     <li>Finally, moving a method up the class hierarchy is not really a breaking change,
 *     but it will trip this test.</li>
 * </ol>
 */
@CacheableTask
public abstract class JarApiComparisonTask extends PrecommitTask {

    @TaskAction
    public void compare() {
        FileCollection fileCollection = getOldJar().get();
        File newJarFile = getNewJar().get().getSingleFile();

        Set<String> oldJarNames = fileCollection.getFiles().stream().map(File::getName).collect(Collectors.toSet());
        if (oldJarNames.size() > 1) {
            throw new IllegalStateException("Expected a single original jar, but found: " + oldJarNames);
        }
        if (oldJarNames.contains(newJarFile.getName())) {
            throw new IllegalStateException(
                "We should be comparing different jars, but original and new jars were both: " + newJarFile.getAbsolutePath()
            );
        }

        JarScanner oldJS = new JarScanner(getOldJar().get().getSingleFile().getPath());
        JarScanner newJS = new JarScanner(newJarFile.getPath());
        try {
            JarScanner.compareSignatures(oldJS.jarSignature(), newJS.jarSignature());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @CompileClasspath
    public abstract Property<FileCollection> getOldJar();

View on GitHub (pinned to db6a809a66)

Solutions

  1. Ensure getOldJar() resolves to a configuration containing exactly one jar — use an artifact-specific configuration, not a transitive classpath.
  2. If using a DRA download, ensure the task type's getOutputs() contains only the jar (the code comment explains DownloadMavenJarTask was created for this reason — line 616).
  3. Inspect the set in the error message to identify the extra files; trace which dependency/configuration added them.
  4. Use builtBy and artifact type attributes to narrow the configuration to a single file.

Example fix

// before: oldJar resolves a transitive configuration
getOldJar().from(configurations.named("bwcOldApi"))  // pulls transitives → multiple files

// after: create a single-artifact configuration
configurations.register("bwcOldApiJar") {
  it.canBeConsumed = false
  it.canBeResolved = true
  it.dependencies.add(project.dependencies.create("org.elasticsearch:elasticsearch-server:${oldVersion}@jar"))
}
getOldJar().from(configurations.named("bwcOldApiJar"))
Defensive patterns

Strategy: validation

Validate before calling

Set<File> files = getOldJar().get().getFiles();
if (files.size() != 1) {
    throw new IllegalStateException("oldJar must resolve to exactly one file, found " + files.size() + ": " + files);
}

Prevention

When it happens

Trigger: At line 70, fileCollection.getFiles() is mapped to a Set<String> of filenames. If size > 1 (line 71), the exception fires. This happens when getOldJar() resolves a configuration that contains multiple jars — e.g., a compile classpath instead of a single artifact, or a configuration that pulls transitive jars.

Common situations: The oldJar property was wired to a resolvable configuration that includes transitive dependencies; the DRA/BWC dependency resolution returned both the jar and a parent directory artifact; a Copy task's output directory leaked into the file collection (noted in the code comment at line 611-613); misconfiguration where oldJar is a classpath rather than a single artifact.

Related errors


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