oracle/graal · error · RedefinitionException
MethodDeleted
MethodDeleted
Error message
class redefinition failed: attempted to delete a method
What it means
RedefinitionException(RedefinitionError.MethodDeleted) thrown by detectClassChanges when JVMTI restrictions are on and the matching phase leaves old methods unmatched (oldMethods non-empty after pairing), i.e. the replacement class omits at least one method present in the original. Before throwing, the removed methods were recorded via collectedChanges.addRemovedMethod, but under jvmtiRestrictions deletion is fatal.
Source
Thrown at espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/ClassRedefinition.java:576
for (ParserMethod changed : newMethods) {
for (int i = 0; i < newParserMethods.length; i++) {
if (newParserMethods[i] == changed) {
collectedChanges.addNewMethod(finalMethods[i]);
break;
}
}
}
} else {
collectedChanges.addNewMethods(newMethods);
}
for (Method oldMethod : oldMethods) {
collectedChanges.addRemovedMethod(oldMethod.getMethodVersion());
}
if (!oldMethods.isEmpty()) {
if (jvmtiRestrictions) {
throw new RedefinitionException(RedefinitionError.MethodDeleted);
}
result = ClassChange.REMOVE_METHOD;
} else if (!newMethods.isEmpty()) {
if (jvmtiRestrictions) {
throw new RedefinitionException(RedefinitionError.MethodAdded);
}
result = ClassChange.ADD_METHOD;
}
if (isPatched) {
result = ClassChange.CLASS_NAME_CHANGED;
}
// detect field changes
Field[] oldFields = oldKlass.getDeclaredFields();
ParserField[] newFields = newParserKlass.getFields();
ArrayList<Field> oldFieldsList = new ArrayList<>(Arrays.asList(oldFields));View on GitHub (pinned to a66e9ccd1d)
Solutions
- Keep every existing method (same name and exact descriptor) in the replacement class; delete methods only across a restart.
- If a signature must change, add the new signature while temporarily retaining the old one, then clean up after restart.
- Use Espresso unrestricted redefinition (not JVMTI mode) if method removal must be hotswapped.
Example fix
// before: v2 removed `int compute(int)`
// after: keep both during hotswap
class Service {
int compute(int x) { return computeLong(x, 0); } // old signature retained
long computeLong(int x, long y) { ... } // new signature added
} Defensive patterns
Strategy: validation
Validate before calling
// diff method sets (name+descriptor) before submitting bytes
Set<String> oldSigs = methodsOf(oldBytes);
Set<String> newSigs = methodsOf(newBytes);
Set<String> deleted = new HashSet<>(oldSigs); deleted.removeAll(newSigs);
if (!deleted.isEmpty()) {
throw new UnsupportedOperationException("methods removed, restart required: " + deleted);
} Try / catch
catch (RedefinitionException e) {
if (e.getError() == RedefinitionError.MethodDeleted) { /* restore methods or restart */ }
} Prevention
- In hotswap workflows, only edit method bodies.
- Automate a signature-diff check in the reload pipeline to fail fast with a clear message.
When it happens
Trigger: Restricted redefinition where a method present in the old class has no same-name-and-descriptor counterpart in the new bytes: method removed, renamed, or its signature changed so it no longer matches.
Common situations: Deleting a method during development and hotswapping; changing a parameter type so the old descriptor disappears; IDE refactor-rename applied via hot reload; source-level 'unused method' cleanup hitting a running context.
Related errors
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/2b2b54c72a54a10e.
Report an issue: GitHub.