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

  1. Keep the NestHost attribute unchanged in the replacement class file (stay in the same nest) and retry.
  2. If the nest structure must change, restart the context / full class load instead of using restricted hotswap.
  3. 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

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.