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
- Read the message: for each mismatched class it prints [class='...', expected='<hex>', found='<hex>']. Decide whether the new bytes are acceptable.
- 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).
- If the transform still holds, update the PatcherInfo's classSha256 for each affected class to the 'found' digest and re-run.
- Pin the dependency version back to the one the patcher was authored for if the bump was unintentional.
- 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
- Pin dependency versions so the patched jar does not drift underneath the patcher.
- When bumping a patched dependency, re-derive and re-test the ASM transform, then update the recorded SHA-256 digests.
- Add a test that asserts every PatcherInfo's digest matches the resolved jar bytes.
- Beware non-reproducible upstream rebuilds that change bytes at the same version.
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
- error patching [%s]: the jar does not contain [%s]
- Failed to patch archive
- Failed to unpack {} with checksum {}
- Two different notices exist for dependency '${name}': ${prev
- Unknown license for license file: {}
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/dcc9708de16b0ba1.
Report an issue: GitHub.