{"record":{"id":"1b253cc0f46be741","repo":"oracle/graal","slug":"schemachanged","errorCode":"SchemaChanged","errorMessage":"class redefinition failed: attempted to change the schema (add/remove fields)","messagePattern":"class redefinition failed: attempted to change the schema \\(add/remove fields\\)","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":632,"sourceCode":"                    // field pointer will have a changed type. Hence we should mark it as a new\n                    // field.\n                    Matcher matcher = InnerClassRedefiner.ANON_INNER_CLASS_PATTERN.matcher(oldField.getType().toString());\n                    if (isPatched && matcher.matches()) {\n                        break;\n                    }\n                    oldFieldsIt.remove();\n                    newFieldsIt.remove();\n                    break;\n                }\n            }\n        }\n\n        if (!newFieldsList.isEmpty()) {\n            if (jvmtiRestrictions) {\n                // only restrict is there's actual new fields, not only fields with constant value\n                // attribute changes\n                if (newFieldsList.size() != acceptedChanges.numAcceptedFields) {\n                    throw new RedefinitionException(RedefinitionError.SchemaChanged);\n                }\n            }\n            if (isPatched) {\n                ParserField[] finalFields = finalParserKlass.getFields();\n                // lookup the final new field based on the index in the parser field array\n                for (ParserField parserField : newFieldsList) {\n                    for (int i = 0; i < newFields.length; i++) {\n                        if (parserField == newFields[i]) {\n                            collectedChanges.addNewField(finalFields[i]);\n                            break;\n                        }\n                    }\n                }\n            } else {\n                collectedChanges.addNewFields(newFieldsList);\n            }\n            result = ClassChange.SCHEMA_CHANGE;\n        }","sourceCodeStart":614,"sourceCodeEnd":650,"githubUrl":"https://github.com/oracle/graal/blob/a66e9ccd1d7bf2552883939aa0788dfd0e294aab/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/ClassRedefinition.java#L614-L650","documentation":"RedefinitionException(RedefinitionError.SchemaChanged) thrown during field-change detection under JVMTI restrictions when the replacement class has leftover new fields that are not merely accepted constant-value-attribute changes (newFieldsList.size() != acceptedChanges.numAcceptedFields). JVMTI redefinition forbids adding fields to the object schema.","triggerScenarios":"Restricted hotswap where the new class declares fields the old class lacked — new instance or static fields, excluding the special-cased constant-value-only changes that are accepted.","commonSituations":"Adding a field to hold new state during development and hotswapping; Lombok/IDE generating new fields between saves; caching/memoization fields added by instrumentation agents.","solutions":["Keep the field set unchanged for restricted hotswaps; only constant values of existing static final fields may differ.","Move new state into a separate newly-loaded class (e.g. a side-table keyed by instance) if it must be introduced without restart.","Restart the context or use unrestricted redefinition when fields must genuinely be added."],"exampleFix":"// before: hotswap adds `private int cache;` to the class\n// after: externalize new state\nclass CacheSideTable {  // new class, fresh-loaded\n    static final Map<MyClass, Integer> CACHE = new WeakHashMap<>();\n}","handlingStrategy":"validation","validationCode":"// diff field sets (name+type) before hotswap\nSet<String> added = fieldsOf(newBytes); added.removeAll(fieldsOf(oldBytes));\nadded.removeAll(constantValueChangedOnly(oldBytes, newBytes)); // accepted changes\nif (!added.isEmpty()) {\n    throw new UnsupportedOperationException(\"fields added, restart required: \" + added);\n}","typeGuard":null,"tryCatchPattern":"catch (RedefinitionException e) {\n    if (e.getError() == RedefinitionError.SchemaChanged) { /* restart context */ }\n}","preventionTips":["Only constant values of existing static final fields may change in a swap.","Externalize new state into side tables / helper classes."],"tags":["espresso","hotswap","class-redefinition","jvmti","fields","schema"],"backgroundTag":null,"analyzedSha":"a66e9ccd1d7bf2552883939aa0788dfd0e294aab","analyzedAt":"2026-08-14T13:58:47.161Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}