oracle/graal · error · RedefinitionException
ClassModifiersChanged
ClassModifiersChanged
Error message
class redefinition failed: attempted to change the class modifiers
What it means
RedefinitionException(RedefinitionError.ClassModifiersChanged) thrown under JVMTI restrictions when the class-level access/modifier flags differ between oldParserKlass and newParserKlass (e.g. public, final, abstract, interface bit changes). Without restrictions the change would just be classified as SCHEMA_CHANGE; with jvmtiRestrictions it is rejected outright.
Source
Thrown at espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/ClassRedefinition.java:667
result = ClassChange.SCHEMA_CHANGE;
}
if (!oldFieldsList.isEmpty()) {
if (jvmtiRestrictions) {
// only restrict is there's actual removed fields, not only fields with constant
// value attribute changes
if (oldFieldsList.size() != acceptedChanges.numAcceptedFields) {
throw new RedefinitionException(RedefinitionError.SchemaChanged);
}
}
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();View on GitHub (pinned to a66e9ccd1d)
Solutions
- Emit replacement bytes with the identical class-level modifiers; restrict hotswap edits to method bodies and constant values.
- Apply modifier changes at restart, where the class is loaded anew.
- If live modifier changes are required, use Espresso's unrestricted redefinition mode.
Example fix
// before: `public class Foo` -> hotswap with `public final class Foo` // after: keep `public class Foo` for the hotswap; add `final` on restart
Defensive patterns
Strategy: validation
Validate before calling
// compare class access flags (bytes offset 0..1 after constant pool via a parser)
int oldFlags = classFlagsOf(oldBytes);
int newFlags = classFlagsOf(newBytes);
if (oldFlags != newFlags) {
throw new UnsupportedOperationException("class modifiers changed, restart required");
} Try / catch
catch (RedefinitionException e) {
if (e.getError() == RedefinitionError.ClassModifiersChanged) { /* restart or revert flags */ }
} Prevention
- Do not toggle final/abstract/visibility during hotswap sessions.
- Beware bytecode processors that silently rewrite flags (e.g. mock/AOP agents).
When it happens
Trigger: Restricted hotswap where the replacement class file has different class-level flags: adding/removing final or abstract, changing visibility, or altering the interface bit.
Common situations: Making a class final (or un-finaling it to allow mocking/subclassing) and hotswapping; toggling abstract during a refactor; annotation-processing or AOP tools rewriting class flags.
Related errors
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/e9dde895179f6421.
Report an issue: GitHub.