oracle/graal · error · RedefinitionException
ClassAttributeChanged
ClassAttributeChanged
Error message
class redefinition failed: attempted to change the class NestHost, NestMembers, Record, or PermittedSubclasses attribute
What it means
RedefinitionException with RedefinitionError.ClassAttributeChanged thrown by detectClassChanges when JVMTI-style restrictions are enabled and the NestHost attribute differs between the old class and the replacement bytes (compared via attrChanged over both constant pools). JVMTI class redefinition forbids changing nest topology; only method bodies and limited constant-pool-preserving changes are allowed.
Source
Thrown at espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/ClassRedefinition.java:472
throw new RedefinitionException(RedefinitionError.CircularClassDefinition);
} catch (EspressoClassLoadingException e) {
throw new RedefinitionException(RedefinitionError.FailsVerification, e.getMessage());
}
}
// detect all types of class changes, but return early when a change that require arbitrary
// changes
private static ClassChange detectClassChanges(ParserKlass newParserKlass, ObjectKlass oldKlass, DetectedChange collectedChanges, ParserKlass finalParserKlass, boolean jvmtiRestrictions)
throws RedefinitionException {
if (oldKlass.getSuperKlass() == oldKlass.getMeta().java_lang_Enum) {
detectInvalidEnumConstantChanges(newParserKlass, oldKlass);
}
ConstantPool oldConstantPool = oldKlass.getConstantPool();
ConstantPool newConstantPool = newParserKlass.getConstantPool();
// detect invalid attribute changes for jvmti restrictions
if (jvmtiRestrictions) {
if (attrChanged(oldKlass.getAttribute(NestHostAttribute.NAME), newParserKlass.getAttribute(NestHostAttribute.NAME), oldConstantPool, newConstantPool)) {
throw new RedefinitionException(RedefinitionError.ClassAttributeChanged);
}
if (attrChanged(oldKlass.getAttribute(NestMembersAttribute.NAME), newParserKlass.getAttribute(NestMembersAttribute.NAME), oldConstantPool, newConstantPool)) {
throw new RedefinitionException(RedefinitionError.ClassAttributeChanged);
}
if (attrChanged(oldKlass.getAttribute(RecordAttribute.NAME), newParserKlass.getAttribute(RecordAttribute.NAME), oldConstantPool, newConstantPool)) {
throw new RedefinitionException(RedefinitionError.ClassAttributeChanged);
}
if (attrChanged(oldKlass.getAttribute(PermittedSubclassesAttribute.NAME), newParserKlass.getAttribute(PermittedSubclassesAttribute.NAME), oldConstantPool, newConstantPool)) {
throw new RedefinitionException(RedefinitionError.ClassAttributeChanged);
}
}
ClassChange result = ClassChange.NO_CHANGE;
ParserKlass oldParserKlass = oldKlass.getLinkedKlass().getParserKlass();
boolean isPatched = finalParserKlass != null;
// detect method changes (including constructors)
ParserMethod[] newParserMethods = newParserKlass.getMethods();View on GitHub (pinned to a66e9ccd1d)
Solutions
- Keep the NestHost attribute unchanged in the replacement class file (stay in the same nest) and retry.
- If the nest structure must change, restart the context / full class load instead of using restricted hotswap.
- If the broader feature (nest changes during redefinition) is genuinely needed, use Espresso's unrestricted redefinition mode rather than JVMTI-restricted mode.
Example fix
// before: replacement bytes declare a different NestHost // (e.g. moved the class out of its nest) // after: keep the class in the same nest; host class unchanged // javac regenerates identical NestHost when the nesting is untouched
Defensive patterns
Strategy: validation
Validate before calling
// before restricted redefinition, diff the nest attributes yourself
String oldHost = readAttribute(oldBytes, "NestHost");
String newHost = readAttribute(newBytes, "NestHost");
if (!Objects.equals(oldHost, newHost)) {
throw new UnsupportedOperationException("nest change requires restart");
} Try / catch
catch (RedefinitionException e) {
if (e.getError() == RedefinitionError.ClassAttributeChanged) { /* fall back to full restart */ }
} Prevention
- Treat hotswap as body-only edits; structural changes go through restart.
- Configure reload tooling to detect structural diffs and auto-restart instead of swap.
When it happens
Trigger: Calling Espresso class redefinition with jvmtiRestrictions=true while the new class file has a different NestHost attribute (class moved into or out of a nest, or pointed at a different host class).
Common situations: Moving a nested class to top-level (or vice versa) and hotswapping it; compilers/agents rewriting nest attributes; Kotlin/Java nesting changes after a refactor submitted as a hotswap instead of a restart.
Related errors
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/f1201b4bfb85d975.
Report an issue: GitHub.