elastic/elasticsearch · error · IllegalArgumentException

Error patching JAR [%s]: SHA256 digest mismatch (%s). This J

Error message

Error patching JAR [%s]: SHA256 digest mismatch (%s). This JAR was updated to a version that contains different classes, for which this patcher was not designed. Please check if the patcher still applies correctly, and update the SHA256 digest(s).

What it means

Thrown by Utils.patchJar after rewriting a JAR when one or more classes targeted by a patcher have a SHA-256 digest that does not match the digest recorded in the PatcherInfo. The patcher is an ASM bytecode transform authored against a specific class shape; the digest guard ensures it is only applied to the exact bytes it was validated on. A mismatch means the upstream dependency shipped different class bytes (a version bump or repackaging) and the patch must be re-validated or the digest updated.

Source

Thrown at build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/dependencies/patches/Utils.java:139

                                    }
                                }
                                manifestEntry.setValue(nonSignatureAttributes);
                            }
                            manifest.write(jos);
                        } else if (unsignJar == false || entryName.matches("META-INF/.*\\.SF") == false) {
                            // Read the entry's data and write it to the new JAR
                            is.transferTo(jos);
                        }
                    }
                }
                jos.closeEntry();
            }
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }

        if (mismatchedClasses.isEmpty() == false) {
            throw new IllegalArgumentException(
                String.format(
                    Locale.ROOT,
                    """
                        Error patching JAR [%s]: SHA256 digest mismatch (%s). This JAR was updated to a version that contains different \
                        classes, for which this patcher was not designed. Please check if the patcher still \
                        applies correctly, and update the SHA256 digest(s).""",
                    inputFile.getName(),
                    mismatchedClasses.stream().map(MismatchInfo::toString).collect(Collectors.joining())
                )
            );
        }

        if (classPatchers.isEmpty() == false) {
            throw new IllegalArgumentException(
                String.format(
                    Locale.ROOT,
                    "error patching [%s]: the jar does not contain [%s]",
                    inputFile.getName(),

View on GitHub (pinned to db6a809a66)

Solutions

  1. Read the message: for each mismatched class it prints [class='...', expected='<hex>', found='<hex>']. Decide whether the new bytes are acceptable.
  2. Verify the patcher's ASM transform still applies correctly to the new class bytes (run the build's patch tests / re-derive the transform against the current jar).
  3. If the transform still holds, update the PatcherInfo's classSha256 for each affected class to the 'found' digest and re-run.
  4. Pin the dependency version back to the one the patcher was authored for if the bump was unintentional.
  5. If using unsignJar=true on a signed JAR, confirm the input jar is the exact signed artifact the patcher expects (signature side-channels aside).

Example fix

// before: patcher recorded digest for lucene-core 9.10.0
new PatcherInfo("org/apache/lucene/util/Foo.class",
    HexFormat.of().parseHex("aaaa..."),  // old expected
    visitor);
// after: bump the digest to the 'found' value once the transform is re-validated
new PatcherInfo("org/apache/lucene/util/Foo.class",
    HexFormat.of().parseHex("bbbb..."),  // matches new jar bytes
    visitor);
Defensive patterns

Strategy: validation

Validate before calling

import java.security.MessageDigest;
import java.util.HexFormat;
import java.util.jar.JarFile;
boolean digestsMatch(File jar, Collection<PatcherInfo> patchers) throws Exception {
    MessageDigest sha = MessageDigest.getInstance("SHA-256");
    var byName = patchers.stream().collect(
        java.util.stream.Collectors.toMap(PatcherInfo::jarEntryName, p -> p));
    try (var jf = new JarFile(jar)) {
        for (var name : byName.keySet()) {
            var e = jf.getJarEntry(name);
            if (e == null) return false;
            byte[] bytes = jf.getInputStream(e).readAllBytes();
            if (!byName.get(name).matches(sha.digest(bytes))) return false;
        }
    }
    return true;
}
// assert digestsMatch(inputJar, patchers);  // before invoking Utils.patchJar

Prevention

When it happens

Trigger: patchJar(inputFile, outputFile, patchers, unsignJar) iterates JAR entries; for each entry matching a PatcherInfo.jarEntryName it computes SHA_256.digest(classBytes) and compares via classPatcher.matches(classSha256). On mismatch it records a MismatchInfo(jarEntryName, expected, found) without applying the transform. After the JAR is fully copied, if mismatchedClasses is non-empty it throws with each class's expected vs found hex digests.

Common situations: A dependency version was bumped (e.g. lucene-core, or a shaded jar) so the target class bytes changed; the upstream published a repackaged/rebuilt artifact with the same version but different bytes (reproducibility gap); the patcher's recorded digest was for a different classifier/os; a transitive resolution pulled a different jar than the one the patcher targeted.

Related errors


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