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

  1. Recompile the subclass against the current version of the superclass and remove/rename the overriding method.
  2. Align jar versions so the superclass without 'final' (or the subclass without the override) is used consistently.
  3. If you own the library, avoid adding 'final' to overridable methods in a patch release.
  4. 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

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


AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14). Data as JSON: /api/errors/239bc4b93ebf535c. Report an issue: GitHub.