oracle/graal · error · RedefinitionException
SchemaChanged
SchemaChanged
Error message
class redefinition failed: attempted to change the schema (add/remove fields)
What it means
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.
Source
Thrown at espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/redefinition/ClassRedefinition.java:632
// field pointer will have a changed type. Hence we should mark it as a new
// field.
Matcher matcher = InnerClassRedefiner.ANON_INNER_CLASS_PATTERN.matcher(oldField.getType().toString());
if (isPatched && matcher.matches()) {
break;
}
oldFieldsIt.remove();
newFieldsIt.remove();
break;
}
}
}
if (!newFieldsList.isEmpty()) {
if (jvmtiRestrictions) {
// only restrict is there's actual new fields, not only fields with constant value
// attribute changes
if (newFieldsList.size() != acceptedChanges.numAcceptedFields) {
throw new RedefinitionException(RedefinitionError.SchemaChanged);
}
}
if (isPatched) {
ParserField[] finalFields = finalParserKlass.getFields();
// lookup the final new field based on the index in the parser field array
for (ParserField parserField : newFieldsList) {
for (int i = 0; i < newFields.length; i++) {
if (parserField == newFields[i]) {
collectedChanges.addNewField(finalFields[i]);
break;
}
}
}
} else {
collectedChanges.addNewFields(newFieldsList);
}
result = ClassChange.SCHEMA_CHANGE;
}View on GitHub (pinned to a66e9ccd1d)
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.
Example fix
// before: hotswap adds `private int cache;` to the class
// after: externalize new state
class CacheSideTable { // new class, fresh-loaded
static final Map<MyClass, Integer> CACHE = new WeakHashMap<>();
} Defensive patterns
Strategy: validation
Validate before calling
// diff field sets (name+type) before hotswap
Set<String> added = fieldsOf(newBytes); added.removeAll(fieldsOf(oldBytes));
added.removeAll(constantValueChangedOnly(oldBytes, newBytes)); // accepted changes
if (!added.isEmpty()) {
throw new UnsupportedOperationException("fields added, restart required: " + added);
} Try / catch
catch (RedefinitionException e) {
if (e.getError() == RedefinitionError.SchemaChanged) { /* restart context */ }
} Prevention
- Only constant values of existing static final fields may change in a swap.
- Externalize new state into side tables / helper classes.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/1b253cc0f46be741.
Report an issue: GitHub.