elastic/elasticsearch · error · IllegalStateException
Classes from a previous version not found: ${deletedClasses}
Error message
Classes from a previous version not found: ${deletedClasses} What it means
Thrown as IllegalStateException by JarScanner.compareSignatures() when the set of class names in the old (reference) jar is not a subset of the class names in the new jar. Removing a public class from a stable-API jar is a backwards-incompatible change; the comparison treats any deleted class as a violation. The error lists the deleted class names so the developer knows exactly what was removed.
Source
Thrown at build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/JarApiComparisonTask.java:212
/**
* Comparison: The signatures are maps of class names to public class, field, or method
* declarations.
* </p>
* First, we check that the new jar signature contains all the same classes
* as the old jar signature. If not, we return an error.
* </p>
* Second, we iterate over the signature for each class. If a signature from the old
* jar is absent in the new jar, we add it to our list of errors.
* </p>
* Note that it is fine for the new jar to have additional elements, as this
* is backwards compatible.
*/
public static void compareSignatures(Map<String, Set<String>> oldSignature, Map<String, Set<String>> newSignature) {
Set<String> deletedClasses = new HashSet<>(oldSignature.keySet());
deletedClasses.removeAll(newSignature.keySet());
if (deletedClasses.size() > 0) {
throw new IllegalStateException("Classes from a previous version not found: " + deletedClasses);
}
Map<String, Set<String>> deletedMembersMap = new HashMap<>();
for (Map.Entry<String, Set<String>> entry : oldSignature.entrySet()) {
Set<String> deletedMembers = new HashSet<>(entry.getValue());
deletedMembers.removeAll(newSignature.get(entry.getKey()));
if (deletedMembers.size() > 0) {
deletedMembersMap.put(entry.getKey(), Set.copyOf(deletedMembers));
}
}
if (deletedMembersMap.size() > 0) {
throw new IllegalStateException(
"Classes from a previous version have been modified, violating backwards compatibility: " + deletedMembersMap
);
}
}
}
}View on GitHub (pinned to db6a809a66)
Solutions
- For each deleted class in the error, decide: was the removal intentional? If not, restore the class or add a deprecated stub.
- If a class was moved/renamed, keep a deprecated forwarding class at the old location to preserve binary compatibility.
- If the removal is intentional and the API check should be updated, bump the stable API baseline reference jar to the current version (acknowledging the break).
- Verify the new jar build includes all expected classes — check the jar task's includes/excludes and source set.
Example fix
// before: public class Foo removed from stable API
// old jar: org/elasticsearch/Foo.class
// new jar: (missing) → throws
// after: keep a deprecated stub for binary compatibility
package org.elasticsearch;
@Deprecated(forRemoval = true)
public class Foo {
public Foo() { /* deprecated shim */ }
} Defensive patterns
Strategy: try-catch
Validate before calling
Set<String> deleted = new HashSet<>(oldSignature.keySet());
deleted.removeAll(newSignature.keySet());
if (deleted.isEmpty() == false) {
System.err.println("API break — deleted classes: " + deleted);
// decide: restore classes, bump baseline, or document intentional removal
} Try / catch
try {
JarScanner.compareSignatures(oldJS.jarSignature(), newJS.jarSignature());
} catch (IllegalStateException e) {
// e.getMessage() lists deleted classes/members
throw new GradleException("Stable API backwards-compatibility violation: " + e.getMessage(), e);
} Prevention
- Never delete or rename public stable-API classes; keep deprecated stubs for binary compatibility.
- Run JarApiComparisonTask in CI on every PR touching stable API modules.
- When moving a class, leave a deprecated forwarding class at the old location.
- Bump the API baseline jar deliberately and document the breaking change when intentional.
When it happens
Trigger: compareSignatures (line 208) computes deletedClasses = oldSignature.keySet() - newSignature.keySet(). If non-empty (line 211), it throws with the set. Each entry is a class path (e.g., 'org/elasticsearch/X.class') present in the old jar but absent from the new. Triggered by deleting a public class, moving it to a different package, or renaming it.
Common situations: A public stable-API class was removed or renamed between versions; a class was moved to a different package (counts as delete + add); a class was made non-public or excluded from the jar; the new jar build accidentally excluded a package; refactoring merged a class into another without keeping the old name.
Related errors
- Expected a single original jar, but found: ${oldJarNames}
- We should be comparing different jars, but original and new
- Unable to resolve to resolve bwc versions from versionsFile.
- Ran out of nodes to take to the next version
- Ran out of versions to go to for {}
AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12).
Data as JSON: /api/errors/e477825adb790a5a.
Report an issue: GitHub.