java-native-access/jna · error · java.lang.IllegalArgumentException

Unrecognized proxy handler: ih

Error message

Unrecognized proxy handler: ih

What it means

Native.getLibrary() (or a similar API taking a loaded library proxy) requires that the object be a JDK dynamic Proxy whose InvocationHandler is a com.sun.jna.Library.Handler. JNA only understands its own handler, which carries the NativeLibrary and call metadata needed to re-wrap the library. Any other InvocationHandler (or a non-proxy object that slipped past the earlier check) makes the handler unrecognizable, so JNA refuses to proceed.

Source

Thrown at src/com/sun/jna/Native.java:1310

    /**
     * Returns a synchronized (thread-safe) library backed by the specified
     * library.  This wrapping will prevent simultaneous invocations of any
     * functions mapped to a given {@link NativeLibrary}.  Note that the
     * native library may still be sensitive to being called from different
     * threads.
     * <p>
     * @param  library the library to be "wrapped" in a synchronized library.
     * @return a synchronized view of the specified library.
     */
    public static Library synchronizedLibrary(final Library library) {
        Class<?> cls = library.getClass();
        if (!Proxy.isProxyClass(cls)) {
            throw new IllegalArgumentException("Library must be a proxy class");
        }
        InvocationHandler ih = Proxy.getInvocationHandler(library);
        if (!(ih instanceof Library.Handler)) {
            throw new IllegalArgumentException("Unrecognized proxy handler: " + ih);
        }
        final Library.Handler handler = (Library.Handler)ih;
        InvocationHandler newHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                synchronized(handler.getNativeLibrary()) {
                    return handler.invoke(library, method, args);
                }
            }
        };
        return (Library)Proxy.newProxyInstance(cls.getClassLoader(),
                                               cls.getInterfaces(),
                                               newHandler);
    }

    /** If running web start, determine the location of a given native
     * library.  This value may be used to properly set
     * <code>jna.library.path</code> so that JNA can load libraries identified

View on GitHub (pinned to d036ad9781)

Solutions

  1. Pass the original JNA-created proxy (the object returned by Native.load/Native.loadLibrary) instead of a re-wrapped proxy.
  2. Apply custom behavior (logging, caching) inside a Callback implemented in the library interface rather than by wrapping the proxy.
  3. When delegating, delegate to a Library.Handler-based proxy: create it via Native.load with the same interface and native library, and never replace its InvocationHandler.
  4. Keep the JNA proxy in a dedicated field and pass that field to JNA APIs, using the custom wrapper only in application code.

Example fix

// before
MyLib wrapped = (MyLib) Proxy.newProxyInstance(cl,
    new Class[]{MyLib.class}, new LoggingHandler(jnaLib));
Native.getNativeLibrary(wrapped); // throws: handler is LoggingHandler

// after
MyLib jnaLib = Native.load("c", MyLib.class);
Native.getNativeLibrary(jnaLib); // Library.Handler, works
Defensive patterns

Strategy: type-guard

Validate before calling

Object lib = ...;
if (!Proxy.isProxyClass(lib.getClass())
        || !(Proxy.getInvocationHandler(lib) instanceof Library.Handler)) {
    throw new IllegalArgumentException("Pass the original JNA proxy, not a custom wrapper");
}

Type guard

static boolean isJnaLibraryProxy(Object o) {
    return Proxy.isProxyClass(o.getClass())
        && Proxy.getInvocationHandler(o) instanceof Library.Handler;
}

Try / catch

try {
    Native.getNativeLibrary(lib);
} catch (IllegalArgumentException e) {
    if (e.getMessage().startsWith("Unrecognized proxy handler")) {
        lib = Native.load(LIB_NAME, MyLib.class); // recreate from source
    } else { throw e; }
}

Prevention

When it happens

Trigger: Calling Native methods that take a loaded library instance (e.g. Native.getLibrary()/Native.synchronizedLibrary-style APIs) with an object that is not the direct return value of Native.loadLibrary/Native.load, but a library instance that was wrapped in a custom java.lang.reflect.Proxy with a non-Library.Handler InvocationHandler.

Common situations: Wrapping a JNA library interface in a user proxy for logging/metrics/retries, passing a mock or hand-built proxy from a test, or retrieving the library from a DI framework that substituted its own proxy for the JNA proxy.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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