{"record":{"id":"f1201b4bfb85d975","repo":"oracle/graal","slug":"classattributechanged","errorCode":"ClassAttributeChanged","errorMessage":"class redefinition failed: attempted to change the class NestHost, NestMembers, Record, or PermittedSubclasses attribute","messagePattern":"class redefinition failed: attempted to change the class NestHost, NestMembers, Record, or PermittedSubclasses attribute","errorType":"error_code","errorClass":"RedefinitionException","httpStatus":null,"severity":"error","filePath":"espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/ClassRedefinition.java","lineNumber":472,"sourceCode":"            throw new RedefinitionException(RedefinitionError.CircularClassDefinition);\n        } catch (EspressoClassLoadingException e) {\n            throw new RedefinitionException(RedefinitionError.FailsVerification, e.getMessage());\n        }\n    }\n\n    // detect all types of class changes, but return early when a change that require arbitrary\n    // changes\n    private static ClassChange detectClassChanges(ParserKlass newParserKlass, ObjectKlass oldKlass, DetectedChange collectedChanges, ParserKlass finalParserKlass, boolean jvmtiRestrictions)\n                    throws RedefinitionException {\n        if (oldKlass.getSuperKlass() == oldKlass.getMeta().java_lang_Enum) {\n            detectInvalidEnumConstantChanges(newParserKlass, oldKlass);\n        }\n        ConstantPool oldConstantPool = oldKlass.getConstantPool();\n        ConstantPool newConstantPool = newParserKlass.getConstantPool();\n        // detect invalid attribute changes for jvmti restrictions\n        if (jvmtiRestrictions) {\n            if (attrChanged(oldKlass.getAttribute(NestHostAttribute.NAME), newParserKlass.getAttribute(NestHostAttribute.NAME), oldConstantPool, newConstantPool)) {\n                throw new RedefinitionException(RedefinitionError.ClassAttributeChanged);\n            }\n            if (attrChanged(oldKlass.getAttribute(NestMembersAttribute.NAME), newParserKlass.getAttribute(NestMembersAttribute.NAME), oldConstantPool, newConstantPool)) {\n                throw new RedefinitionException(RedefinitionError.ClassAttributeChanged);\n            }\n            if (attrChanged(oldKlass.getAttribute(RecordAttribute.NAME), newParserKlass.getAttribute(RecordAttribute.NAME), oldConstantPool, newConstantPool)) {\n                throw new RedefinitionException(RedefinitionError.ClassAttributeChanged);\n            }\n            if (attrChanged(oldKlass.getAttribute(PermittedSubclassesAttribute.NAME), newParserKlass.getAttribute(PermittedSubclassesAttribute.NAME), oldConstantPool, newConstantPool)) {\n                throw new RedefinitionException(RedefinitionError.ClassAttributeChanged);\n            }\n        }\n\n        ClassChange result = ClassChange.NO_CHANGE;\n        ParserKlass oldParserKlass = oldKlass.getLinkedKlass().getParserKlass();\n        boolean isPatched = finalParserKlass != null;\n\n        // detect method changes (including constructors)\n        ParserMethod[] newParserMethods = newParserKlass.getMethods();","sourceCodeStart":454,"sourceCodeEnd":490,"githubUrl":"https://github.com/oracle/graal/blob/a66e9ccd1d7bf2552883939aa0788dfd0e294aab/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/ClassRedefinition.java#L454-L490","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before: replacement bytes declare a different NestHost\n// (e.g. moved the class out of its nest)\n// after: keep the class in the same nest; host class unchanged\n// javac regenerates identical NestHost when the nesting is untouched","handlingStrategy":"validation","validationCode":"// before restricted redefinition, diff the nest attributes yourself\nString oldHost = readAttribute(oldBytes, \"NestHost\");\nString newHost = readAttribute(newBytes, \"NestHost\");\nif (!Objects.equals(oldHost, newHost)) {\n    throw new UnsupportedOperationException(\"nest change requires restart\");\n}","typeGuard":null,"tryCatchPattern":"catch (RedefinitionException e) {\n    if (e.getError() == RedefinitionError.ClassAttributeChanged) { /* fall back to full restart */ }\n}","preventionTips":["Treat hotswap as body-only edits; structural changes go through restart.","Configure reload tooling to detect structural diffs and auto-restart instead of swap."],"tags":["espresso","hotswap","class-redefinition","jvmti","nestmates"],"backgroundTag":null,"analyzedSha":"a66e9ccd1d7bf2552883939aa0788dfd0e294aab","analyzedAt":"2026-08-14T13:58:47.161Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}