{"record":{"id":"6900b3055836b129","repo":"quarkusio/quarkus","slug":"public-method-methodinfo-cannot-be-proxied-as-i","errorCode":null,"errorMessage":"Public method ${methodInfo} cannot be proxied as it is final","messagePattern":"Public method (.+?) cannot be proxied as it is final","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"core/deployment/src/main/java/io/quarkus/deployment/proxy/ProxyFactory.java","lineNumber":133,"sourceCode":"\n    private void addMethodsOfClass(Class<?> clazz) {\n        addMethodsOfClass(clazz, new HashSet<>());\n    }\n\n    private void addMethodsOfClass(Class<?> clazz, Set<MethodKey> seen) {\n        for (Method methodInfo : clazz.getDeclaredMethods()) {\n            MethodKey key = new MethodKey(methodInfo.getReturnType(), methodInfo.getName(), methodInfo.getParameterTypes());\n            if (seen.contains(key)) {\n                continue;\n            }\n            seen.add(key);\n            if (methodInfo.getName().equals(\"finalize\") && methodInfo.getParameterCount() == 0) {\n                continue;\n            }\n            int modifiers = methodInfo.getModifiers();\n            if (Modifier.isPublic(modifiers) && Modifier.isFinal(modifiers) && !Modifier.isStatic(modifiers)\n                    && clazz != Object.class) {\n                throw new RuntimeException(\"Public method \" + methodInfo + \" cannot be proxied as it is final\");\n            }\n            if (!Modifier.isStatic(modifiers) &&\n                    !Modifier.isFinal(modifiers) &&\n                    !methodInfo.getName().equals(\"<init>\")) {\n                methods.add(methodInfo);\n            }\n        }\n        if (clazz.getSuperclass() != null) {\n            addMethodsOfClass(clazz.getSuperclass(), seen);\n        }\n    }\n\n    public Class<? extends T> defineClass() {\n        synchronized (lock) {\n            if (!classDefined) {\n                doDefineClass();\n                if (injectConstructor == null) {\n                    try {","sourceCodeStart":115,"sourceCodeEnd":151,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/core/deployment/src/main/java/io/quarkus/deployment/proxy/ProxyFactory.java#L115-L151","documentation":"During bytecode recording of a proxy class, ProxyFactory.addMethodsOfClass refuses to proxy a public non-static final method, because a generated subclass proxy cannot override final methods. The superclass's final method would leak through unproxied, silently breaking recording semantics, so the factory fails fast.","triggerScenarios":"A recorder method return type / proxy target class (other than java.lang.Object) declares a public, non-static final method that ProxyFactory tries to add while building the proxy for recorded invocations.","commonSituations":"Using a return type in a @Recorder method (not wrapped in RuntimeValue) whose class has final public methods (e.g. final methods on config/record-like classes); library classes finalized for security.","solutions":["Wrap the value in RuntimeValue<T> in the recorder method signature so only construction is recorded, not method proxying","Remove final from the offending public method if you own the class","Use an interface type or a non-final superclass as the recorder return type","Make the method static if it does not need instance state"],"exampleFix":"// before\n@Recorder\npublic MyConfig config() { return new MyConfig(); } // MyConfig has public final methods\n// after\n@Recorder\npublic RuntimeValue<MyConfig> config() { return new RuntimeValue<>(new MyConfig()); }","handlingStrategy":"type-guard","validationCode":"static boolean proxySafe(Class<?> c) {\n    for (Method m : c.getMethods()) {\n        if (Modifier.isPublic(m.getModifiers()) && Modifier.isFinal(m.getModifiers())\n                && !Modifier.isStatic(m.getModifiers()) && m.getDeclaringClass() != Object.class) {\n            return false;\n        }\n    }\n    return true;\n}","typeGuard":"static boolean proxySafe(Class<?> c) {\n    for (Method m : c.getMethods()) {\n        if (Modifier.isPublic(m.getModifiers()) && Modifier.isFinal(m.getModifiers())\n                && !Modifier.isStatic(m.getModifiers()) && m.getDeclaringClass() != Object.class) {\n            return false;\n        }\n    }\n    return true;\n}","tryCatchPattern":null,"preventionTips":["Prefer RuntimeValue<T> for recorder return types you do not control","Avoid final public methods on recorder-exposed classes","Add a build-time test that instantiates proxies for recorder return types"],"tags":["bytecode-recording","proxy","build-time","recorder"],"backgroundTag":"final-method-not-proxiable","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}