{"record":{"id":"3323471f4bd9fef1","repo":"oracle/graal","slug":"methods-with-same-signature-but-incompatible-re","errorCode":null,"errorMessage":"methods with same signature {} but incompatible return types: {} and others","messagePattern":"methods with same signature (.+?) but incompatible return types: (.+?) and others","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/EspressoForeignProxyGenerator.java","lineNumber":731,"sourceCode":"    private static void checkReturnTypes(List<ProxyMethod> methods) {\n        /*\n         * If there is only one method with a given signature, there cannot be a conflict. This is\n         * the only case in which a primitive (or void) return type is allowed.\n         */\n        if (methods.size() < 2) {\n            return;\n        }\n\n        /*\n         * List of return types that are not yet known to be assignable from (\"covered\" by) any of\n         * the others.\n         */\n        LinkedList<Klass> uncoveredReturnTypes = new LinkedList<>();\n\n        nextNewReturnType: for (ProxyMethod pm : methods) {\n            Klass newReturnType = pm.returnType.getRawType();\n            if (newReturnType.isPrimitive()) {\n                throw new IllegalArgumentException(\n                                \"methods with same signature \" +\n                                                getFriendlyMethodSignature(pm.methodName,\n                                                                pm.parameterTypes) +\n                                                \" but incompatible return types: \" +\n                                                newReturnType.getName() + \" and others\");\n            }\n            boolean added = false;\n\n            /*\n             * Compare the new return type to the existing uncovered return types.\n             */\n            ListIterator<Klass> liter = uncoveredReturnTypes.listIterator();\n            while (liter.hasNext()) {\n                Klass uncoveredReturnType = liter.next();\n\n                /*\n                 * If an existing uncovered return type is assignable to this new one, then we can\n                 * forget the new one.","sourceCodeStart":713,"sourceCodeEnd":749,"githubUrl":"https://github.com/oracle/graal/blob/a66e9ccd1d7bf2552883939aa0788dfd0e294aab/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/interop/EspressoForeignProxyGenerator.java#L713-L749","documentation":"Thrown by EspressoForeignProxyGenerator's return-type coverage check: when two proxied interfaces declare the same method signature but their return types are incompatible, the first failure mode triggers if the newly seen return type is primitive — a primitive can never be 'covered' by a reference return type, so the proxy cannot implement both methods and creation aborts. Message includes the friendly signature and the offending primitive type.","triggerScenarios":"Proxying interfaces where one declares 'int m()' and another declares 'Object m()' (same name and parameter types): the primitive return type hits the isPrimitive() branch and throws. Replicates java.lang.reflect.Proxy's 'methods with same signature but incompatible return types' error.","commonSituations":"Cobbling together proxies over unrelated third-party interfaces that happen to share a method name; version drift where one interface's method signature changed its return type; bridge-style APIs mixing boxed and primitive returns.","solutions":["Do not proxy those two interfaces together — pick one, or wrap one behind an adapter object.","Change one interface's method return type so they are assignable (e.g. both reference types with a common subtype).","If you control the interfaces, rename the colliding method on one of them."],"exampleFix":"// before\ninterface A { int fetch(); }\ninterface B { Object fetch(); }\nproxy(A.class, B.class); // throws\n\n// after\ninterface B { Object fetchValue(); } // renamed, no clash\nproxy(A.class, B.class);","handlingStrategy":"validation","validationCode":"// reject proxies over same-signature methods with primitive vs reference returns\nMap<String, Klass> returnsBySignature = new HashMap<>();\nfor (Method m : allMethods(interfaces)) {\n    String sig = m.getName() + Arrays.toString(m.getParameterTypes());\n    Klass prev = returnsBySignature.putIfAbsent(sig, m.getReturnType());\n    if (prev != null && !prev.isAssignableTo(m.getReturnType()) && !m.getReturnType().isAssignableTo(prev)) {\n        throw new IllegalArgumentException(\"incompatible returns for \" + sig);\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    generator.getProxy(context, loader, interfaces);\n} catch (IllegalArgumentException e) {\n    // message contains the conflicting signature: split the proxy or fix the interfaces\n}","preventionTips":["Do not combine interfaces with same-name, same-parameter methods of incompatible return types.","Watch for primitive vs boxed return drift across library versions.","Prefer adapters over mega-proxies when merging third-party SPIs."],"tags":["proxy","method-signature","return-type","interoperability","espresso"],"backgroundTag":null,"analyzedSha":"a66e9ccd1d7bf2552883939aa0788dfd0e294aab","analyzedAt":"2026-08-14T13:58:47.161Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}