{"record":{"id":"239bc4b93ebf535c","repo":"oracle/graal","slug":"method-from-type-overrides-final-method","errorCode":null,"errorMessage":"Method {}{} from type {} overrides final method {}{} from type {}","messagePattern":"Method (.+?)(.+?) from type (.+?) overrides final method (.+?)(.+?) from type (.+?)","errorType":"exception","errorClass":"MethodTableException","httpStatus":null,"severity":"error","filePath":"espresso-shared/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/vtable/VTable.java","lineNumber":218,"sourceCode":"                }\n            }\n        }\n\n        private void resolveVirtual() throws MethodTableException {\n            List<M> parentTable = targetClass.getParentTable();\n            for (int i = 0; i < parentTable.size(); i++) {\n                M parentMethod = parentTable.get(i);\n                MethodKey k = MethodKey.of(parentMethod);\n                assert locations.containsKey(k) : \"Should have been populated with super table.\";\n                Locations<C, M, F> currentLocations = locations.get(k);\n                // If this class declares a method with same name and signature, it might be the\n                // entry in the vtable for this slot.\n                TableEntry<C, M, F> declaredMethod = currentLocations.target;\n                if (declaredMethod != null) {\n                    assert parentMethod.getDeclaringClass().isInterface() || currentLocations.vLookup(i) == parentMethod : \"Should have been populated with super table.\";\n                    if (canOverride(declaredMethod, parentMethod, i)) {\n                        if (parentMethod.isFinalFlagSet()) {\n                            throw new MethodTableException(\n                                            \"Method \" + declaredMethod.getSymbolicName() + declaredMethod.getSymbolicSignature() +\n                                                            \" from type \" + targetClass.getSymbolicName() +\n                                                            \" overrides final method \" + parentMethod.getSymbolicName() + declaredMethod.getSymbolicSignature() +\n                                                            \" from type \" + parentMethod.getDeclaringClass().getSymbolicName(),\n                                            MethodTableException.Kind.IncompatibleClassChangeError);\n                        }\n                        TableEntryRef<C, M, F> declaredEntry = TableEntryRef.create(declaredMethod);\n                        if (!verbose && sameOverrideAccess(declaredMethod, parentMethod)) {\n                            // If this declared method overrides a method with equivalent access, we\n                            // don't need to add that method at the end.\n                            if (currentLocations.markEquivalentEntry()) {\n                                // Make sure if this method has multiple equivalent entries, only\n                                // one gets to use the vtable index.\n                                declaredEntry.useVTableSlotIndex();\n                            }\n                        }\n                        // Success: write this declared method in the table\n                        vtable.add(declaredEntry);","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/oracle/graal/blob/a66e9ccd1d7bf2552883939aa0788dfd0e294aab/espresso-shared/src/com.oracle.truffle.espresso.shared/src/com/oracle/truffle/espresso/shared/vtable/VTable.java#L200-L236","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before (library v2 made m() final, stale subclass still overrides)\nclass A { final void m() {} }\nclass B extends A { @Override void m() {} } // IncompatibleClassChangeError\n\n// after\nclass A { final void m() {} }\nclass B extends A { /* no override of m() */ }","handlingStrategy":"try-catch","validationCode":"// before loading generated/subclassed bytecode, check overrides against the supertype\nstatic void checkNoFinalOverride(Class<?> superCls, String name, Class<?>... params) throws NoSuchMethodException {\n    for (Class<?> c = superCls; c != null; c = c.getSuperclass()) {\n        java.lang.reflect.Method m = c.getDeclaredMethod(name, params);\n        if (Modifier.isFinal(m.getModifiers())) {\n            throw new IllegalStateException(\"overrides final \" + c.getName() + \".\" + name);\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    Class<?> cls = Class.forName(\"com.example.Sub\");\n} catch (IncompatibleClassChangeError e) {\n    // message: \"... overrides final method ... from type ...\"\n    rebuildAgainstCurrentSuperclass(); // recompile subclass, then retry load\n}","preventionTips":["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."],"tags":["jvm","espresso","vtable","final-method","binary-compatibility"],"backgroundTag":null,"analyzedSha":"a66e9ccd1d7bf2552883939aa0788dfd0e294aab","analyzedAt":"2026-08-14T13:58:47.161Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}