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

  1. Keep every existing method (same name and exact descriptor) in the replacement class; delete methods only across a restart.
  2. If a signature must change, add the new signature while temporarily retaining the old one, then clean up after restart.
  3. 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

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.