java-native-access/jna · error · IllegalArgumentException

Callback argument <nativeParamTypes[i]> requires custom type

Error message

Callback argument <nativeParamTypes[i]> requires custom type conversion

What it means

IllegalArgumentException thrown while registering a native callback when a callback METHOD PARAMETER type does not map to an allowable native type (see isAllowableNativeType: boolean, byte, char, short, int, long, float, double, Pointer, Structure, String/WString/Buffer-backed types, and by-reference wrappers). JNA cannot marshal an arbitrary Java object (e.g. java.lang.StringBuilder, boxed types, custom classes) into a native callback argument without a TypeMapper/custom conversion, so registration fails immediately.

Source

Thrown at src/com/sun/jna/CallbackReference.java:314

            // to match the true Java callback method signature
            if (mapper != null) {
                for (int i=0;i < nativeParamTypes.length;i++) {
                    FromNativeConverter rc = mapper.getFromNativeConverter(nativeParamTypes[i]);
                    if (rc != null) {
                        nativeParamTypes[i] = rc.nativeType();
                    }
                }
                ToNativeConverter tn = mapper.getToNativeConverter(returnType);
                if (tn != null) {
                    returnType = tn.nativeType();
                }
            }
            for (int i=0;i < nativeParamTypes.length;i++) {
                nativeParamTypes[i] = getNativeType(nativeParamTypes[i]);
                if (!isAllowableNativeType(nativeParamTypes[i])) {
                    String msg = "Callback argument " + nativeParamTypes[i]
                        + " requires custom type conversion";
                    throw new IllegalArgumentException(msg);
                }
            }
            returnType = getNativeType(returnType);
            if (!isAllowableNativeType(returnType)) {
                String msg = "Callback return type " + returnType
                    + " requires custom type conversion";
                throw new IllegalArgumentException(msg);
            }
            int flags = DLL_CALLBACK_CLASS != null
                && DLL_CALLBACK_CLASS.isInstance(callback)
                ? Native.CB_OPTION_IN_DLL : 0;
            peer = Native.createNativeCallback(proxy, PROXY_CALLBACK_METHOD,
                                               nativeParamTypes, returnType,
                                               callingConvention, flags,
                                               encoding);
        }
        cbstruct = peer != 0 ? new Pointer(peer) : null;
        if(peer != 0) {

View on GitHub (pinned to d036ad9781)

Solutions

  1. Change the callback parameter to a natively mappable type: primitive (int, double, ...), Pointer, Structure, String, or CallbackReference-supported by-reference types.
  2. If you must receive a custom type, register a TypeMapper (e.g. via Library.OPTION_TYPE_MAPPER or Native.setTypeMapper) that provides ToNativeConverter/FromNativeConverter for that class.
  3. For pointer-sized values use Pointer or long/PointerType instead of boxed Number subclasses; for C strings use String/WString or a byte[]/char[] buffer parameter.

Example fix

// before
public interface ProgressCb extends Callback { void invoke(StringBuilder msg); }
// after
public interface ProgressCb extends Callback { void invoke(Pointer msg); }
// or keep String:
public interface ProgressCb extends Callback { void invoke(String msg); }
Defensive patterns

Strategy: validation

Validate before calling

static void checkParamTypes(Method m) {
    for (Class<?> p : m.getParameterTypes()) {
        if (!(p.isPrimitive() || p == Pointer.class || Structure.class.isAssignableFrom(p)
              || p == String.class || p == WString.class || p == byte[].class || p == char[].class
              || Callback.class.isAssignableFrom(p))) {
            throw new IllegalArgumentException("Unmappable callback arg type: " + p);
        }
    }
}

Type guard

static boolean isMappableNativeParam(Class<?> p) {
    return p.isPrimitive() || p == Pointer.class || Structure.class.isAssignableFrom(p)
        || p == String.class || p == WString.class;
}

Try / catch

try { registerCallback(cb); } catch (IllegalArgumentException e) { if (e.getMessage().contains("requires custom type conversion")) { /* fix signature or add TypeMapper */ } throw e; }

Prevention

When it happens

Trigger: Creating a callback whose interface method declares a parameter of a type outside the allowable native set — e.g. void callback(StringBuilder sb), void callback(Integer i), or a custom POJO — when the callback is registered with the native runtime (Native.createNativeCallback path, first use in Native.loadLibrary'd function or Structure field).

Common situations: Copying Java signatures (java.util.List, StringBuilder, boxed Integer/Double) into a callback meant to receive C arguments; forgetting a TypeMapper on the library options for custom types; typos like using 'Integer' instead of 'int'.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of java-native-access/jna@d036ad9781 (2026-09-12). Data as JSON: /api/errors/93162aced831a483. Report an issue: GitHub.