{"record":{"id":"f01cbef29dcc503c","repo":"java-native-access/jna","slug":"callback-return-type-returntype-requires-custom-type","errorCode":null,"errorMessage":"Callback return type <returnType> requires custom type conversion","messagePattern":"Callback return type <returnType> requires custom type conversion","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"src/com/sun/jna/CallbackReference.java","lineNumber":321,"sourceCode":"                }\n                ToNativeConverter tn = mapper.getToNativeConverter(returnType);\n                if (tn != null) {\n                    returnType = tn.nativeType();\n                }\n            }\n            for (int i=0;i < nativeParamTypes.length;i++) {\n                nativeParamTypes[i] = getNativeType(nativeParamTypes[i]);\n                if (!isAllowableNativeType(nativeParamTypes[i])) {\n                    String msg = \"Callback argument \" + nativeParamTypes[i]\n                        + \" requires custom type conversion\";\n                    throw new IllegalArgumentException(msg);\n                }\n            }\n            returnType = getNativeType(returnType);\n            if (!isAllowableNativeType(returnType)) {\n                String msg = \"Callback return type \" + returnType\n                    + \" requires custom type conversion\";\n                throw new IllegalArgumentException(msg);\n            }\n            int flags = DLL_CALLBACK_CLASS != null\n                && DLL_CALLBACK_CLASS.isInstance(callback)\n                ? Native.CB_OPTION_IN_DLL : 0;\n            peer = Native.createNativeCallback(proxy, PROXY_CALLBACK_METHOD,\n                                               nativeParamTypes, returnType,\n                                               callingConvention, flags,\n                                               encoding);\n        }\n        cbstruct = peer != 0 ? new Pointer(peer) : null;\n        if(peer != 0) {\n            allocatedMemory.put(peer, new WeakReference<>(this));\n            cleanable = Cleaner.getCleaner().register(this, new CallbackReferenceDisposer(cbstruct));\n        }\n    }\n\n    private Class<?> getNativeType(Class<?> cls) {\n        if (Structure.class.isAssignableFrom(cls)) {","sourceCodeStart":303,"sourceCodeEnd":339,"githubUrl":"https://github.com/java-native-access/jna/blob/d036ad9781adad4b66693e8fa7098e4ac665e0a3/src/com/sun/jna/CallbackReference.java#L303-L339","documentation":"IllegalArgumentException thrown while registering a native callback when the callback method's RETURN type does not map to an allowable native type. JNA must convert the Java return value back to a native register value; only primitives, void, Pointer, Structure, String/WString and similar mappable types are supported. Without a TypeMapper/custom converter, any other return type fails registration.","triggerScenarios":"Registering (first use of) a callback whose method returns e.g. Object, StringBuilder, Integer, List, or a custom class — the getNativeType(returnType) result fails isAllowableNativeType during CallbackReference construction.","commonSituations":"Writing callbacks like 'Object invoke(...)' expecting JNA to guess the C return; boxing return values (Boolean/Integer instead of boolean/int); returning application-specific result objects from a C callback.","solutions":["Change the return type to a natively mappable one: void, int, long, boolean, double, float, Pointer, Structure, or String.","Model C conventions directly: return int status codes, use Pointer for handles, and encode results through by-reference arguments (structures/pointers) instead of a complex return value.","If a custom return type is required, attach a TypeMapper with a ToNativeConverter for that type via library options or Native.setTypeMapper."],"exampleFix":"// before\npublic interface CmpCb extends Callback { Object invoke(Pointer a, Pointer b); }\n// after\npublic interface CmpCb extends Callback { int invoke(Pointer a, Pointer b); }","handlingStrategy":"validation","validationCode":"static void checkReturnType(Method m) {\n    Class<?> r = m.getReturnType();\n    if (!(r == void.class || r.isPrimitive() || r == Pointer.class\n          || Structure.class.isAssignableFrom(r) || r == String.class || r == WString.class)) {\n        throw new IllegalArgumentException(\"Unmappable callback return type: \" + r);\n    }\n}","typeGuard":"static boolean isMappableNativeReturn(Class<?> r) {\n    return r == void.class || r.isPrimitive() || r == Pointer.class\n        || Structure.class.isAssignableFrom(r) || r == String.class;\n}","tryCatchPattern":"try { registerCallback(cb); } catch (IllegalArgumentException e) { if (e.getMessage().startsWith(\"Callback return type\")) { /* change return type or add TypeMapper */ } throw e; }","preventionTips":["Return void or C-compatible primitives/Pointers from callbacks.","Do not return boxed types or POJOs; encode results in by-reference arguments.","Add a TypeMapper for custom return conversions.","Smoke-test callback registration at startup."],"tags":["jna","callback","return-type","native-marshalling"],"backgroundTag":"type-mismatch","analyzedSha":"d036ad9781adad4b66693e8fa7098e4ac665e0a3","analyzedAt":"2026-09-12T06:50:59.239Z","contentChangedAt":"2026-09-12T06:50:59.239Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}