oracle/graal · error · MethodTableException
Method {}{} from type {} overrides final method {}{} from ty
Error message
Method {}{} from type {} overrides final method {}{} from type {} What it means
During vtable construction, Espresso found a method declared in the target class with the same name and signature as a final method inherited from a supertype. JVMS 5.3.5 step 4 requires IncompatibleClassChangeError in this case, and VTable throws MethodTableException with that kind.
Source
Thrown at espresso-shared/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/vtable/VTable.java:218
}
}
}
private void resolveVirtual() throws MethodTableException {
List<M> parentTable = targetClass.getParentTable();
for (int i = 0; i < parentTable.size(); i++) {
M parentMethod = parentTable.get(i);
MethodKey k = MethodKey.of(parentMethod);
assert locations.containsKey(k) : "Should have been populated with super table.";
Locations<C, M, F> currentLocations = locations.get(k);
// If this class declares a method with same name and signature, it might be the
// entry in the vtable for this slot.
TableEntry<C, M, F> declaredMethod = currentLocations.target;
if (declaredMethod != null) {
assert parentMethod.getDeclaringClass().isInterface() || currentLocations.vLookup(i) == parentMethod : "Should have been populated with super table.";
if (canOverride(declaredMethod, parentMethod, i)) {
if (parentMethod.isFinalFlagSet()) {
throw new MethodTableException(
"Method " + declaredMethod.getSymbolicName() + declaredMethod.getSymbolicSignature() +
" from type " + targetClass.getSymbolicName() +
" overrides final method " + parentMethod.getSymbolicName() + declaredMethod.getSymbolicSignature() +
" from type " + parentMethod.getDeclaringClass().getSymbolicName(),
MethodTableException.Kind.IncompatibleClassChangeError);
}
TableEntryRef<C, M, F> declaredEntry = TableEntryRef.create(declaredMethod);
if (!verbose && sameOverrideAccess(declaredMethod, parentMethod)) {
// If this declared method overrides a method with equivalent access, we
// don't need to add that method at the end.
if (currentLocations.markEquivalentEntry()) {
// Make sure if this method has multiple equivalent entries, only
// one gets to use the vtable index.
declaredEntry.useVTableSlotIndex();
}
}
// Success: write this declared method in the table
vtable.add(declaredEntry);View on GitHub (pinned to a66e9ccd1d)
Solutions
- Recompile the subclass against the current version of the superclass and remove/rename the overriding method.
- Align jar versions so the superclass without 'final' (or the subclass without the override) is used consistently.
- If you own the library, avoid adding 'final' to overridable methods in a patch release.
- If caused by hot swap, perform a full redefinition of both classes or restart the context.
Example fix
// before (library v2 made m() final, stale subclass still overrides)
class A { final void m() {} }
class B extends A { @Override void m() {} } // IncompatibleClassChangeError
// after
class A { final void m() {} }
class B extends A { /* no override of m() */ } Defensive patterns
Strategy: try-catch
Validate before calling
// before loading generated/subclassed bytecode, check overrides against the supertype
static void checkNoFinalOverride(Class<?> superCls, String name, Class<?>... params) throws NoSuchMethodException {
for (Class<?> c = superCls; c != null; c = c.getSuperclass()) {
java.lang.reflect.Method m = c.getDeclaredMethod(name, params);
if (Modifier.isFinal(m.getModifiers())) {
throw new IllegalStateException("overrides final " + c.getName() + "." + name);
}
}
} Try / catch
try {
Class<?> cls = Class.forName("com.example.Sub");
} catch (IncompatibleClassChangeError e) {
// message: "... overrides final method ... from type ..."
rebuildAgainstCurrentSuperclass(); // recompile subclass, then retry load
} Prevention
- Recompile the whole application (not just changed classes) after a library upgrades a method to final.
- Run a dependency convergence check so only one version of each jar is on the classpath.
- In library maintenance, treat adding 'final' to public/protected methods as a breaking change.
When it happens
Trigger: class B extends A where A declares 'final void m()' and B declares 'void m()'. Happens when A was recompiled with final added (or B compiled against a non-final older A) and only A's class file is refreshed at runtime.
Common situations: Jar version skew (library made a method final in a newer release, stale subclass on classpath); binary-incompatible upgrades; hot code replace where only the superclass is redefined; obfuscators/bytecode weavers that add final or copy methods.
Related errors
- Array type with more than 255 dimensions
- Loading constraint violated !
- New loading constraint violates an older one!
- Cannot bind label to negative position %d
- Out of scratch registers: %s
AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14).
Data as JSON: /api/errors/239bc4b93ebf535c.
Report an issue: GitHub.