oracle/graal · error · RedefinitionException

HierarchyChanged

HierarchyChanged

Error message

class redefinition failed: attempted to change superclass or interfaces

What it means

RedefinitionException(RedefinitionError.HierarchyChanged) thrown under JVMTI restrictions when the replacement class's superclass symbol differs from the original's (newParserKlass.getSuperKlass() vs oldParserKlass.getSuperKlass()). Changing the superclass reshapes the hierarchy; JVMTI redefinition forbids it, while unrestricted mode would proceed to CLASS_HIERARCHY_CHANGED handling.

Source

Thrown at espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/ClassRedefinition.java:678

            collectedChanges.addRemovedFields(oldFieldsList);
            result = ClassChange.SCHEMA_CHANGE;
        }

        // detect class-level changes
        if (newParserKlass.getFlags() != oldParserKlass.getFlags()) {
            if (jvmtiRestrictions) {
                throw new RedefinitionException(RedefinitionError.ClassModifiersChanged);
            }
            result = ClassChange.SCHEMA_CHANGE;
        }

        collectedChanges.addCompatibleFields(compatibleFields);

        // detect changes to superclass and implemented interfaces
        Klass superKlass = oldKlass.getSuperKlass();
        if (!newParserKlass.getSuperKlass().equals(oldParserKlass.getSuperKlass())) {
            if (jvmtiRestrictions) {
                throw new RedefinitionException(RedefinitionError.HierarchyChanged);
            }
            result = ClassChange.CLASS_HIERARCHY_CHANGED;
            superKlass = getLoadedKlass(newParserKlass.getSuperKlass(), oldKlass);
        }
        collectedChanges.addSuperKlass((ObjectKlass) superKlass);

        ObjectKlass[] newSuperInterfaces = oldKlass.getSuperInterfaces();
        if (!Arrays.equals(newParserKlass.getSuperInterfaces(), oldParserKlass.getSuperInterfaces())) {
            if (jvmtiRestrictions) {
                throw new RedefinitionException(RedefinitionError.HierarchyChanged);
            }
            result = ClassChange.CLASS_HIERARCHY_CHANGED;
            newSuperInterfaces = new ObjectKlass[newParserKlass.getSuperInterfaces().length];
            for (int i = 0; i < newParserKlass.getSuperInterfaces().length; i++) {
                newSuperInterfaces[i] = (ObjectKlass) getLoadedKlass(newParserKlass.getSuperInterfaces()[i], oldKlass);
            }
        }
        collectedChanges.addSuperInterfaces(newSuperInterfaces);

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Keep the same superclass in hotswapped versions; hierarchy edits require a restart.
  2. Load the modified class as a brand-new class name instead of redefining the existing one.
  3. Use unrestricted Espresso redefinition when superclass changes must be hotswapped.

Example fix

// before: `class Task` -> hotswap as `class Task extends BaseTask`
// after: keep `class Task` for hotswap; introduce hierarchy on restart
Defensive patterns

Strategy: validation

Validate before calling

String oldSuper = superNameOf(oldBytes);
String newSuper = superNameOf(newBytes);
if (!Objects.equals(oldSuper, newSuper)) {
    throw new UnsupportedOperationException("superclass changed from " + oldSuper + " to " + newSuper + "; restart required");
}

Try / catch

catch (RedefinitionException e) {
    if (e.getError() == RedefinitionError.HierarchyChanged) { /* restart context */ }
}

Prevention

When it happens

Trigger: Restricted hotswap where the new bytes extend a different class (superclass swapped, added, or changed from java.lang.Object to a user class).

Common situations: Introducing a base class into an existing hierarchy during refactoring and hotswapping the child; framework-generated subclasses changing parents between builds; copy-paste development changing extends clauses.

Related errors


AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14). Data as JSON: /api/errors/decd509afb3374de. Report an issue: GitHub.