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 identifiedView on GitHub (pinned to d036ad9781)
Solutions
- Pass the original JNA-created proxy (the object returned by Native.load/Native.loadLibrary) instead of a re-wrapped proxy.
- Apply custom behavior (logging, caching) inside a Callback implemented in the library interface rather than by wrapping the proxy.
- 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.
- 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
- Never wrap the JNA proxy in your own java.lang.reflect.Proxy before handing it back to JNA APIs.
- Store the raw Native.load result in a dedicated field for JNA API use.
- Add logging/metrics inside the mapped interface (default methods) instead of proxying.
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
- Object is not a properly initialized Library interface insta
- Invalid library name "<libname>"
- <libname> does not implement an interface: <interfaceClassNa
- There is an incompatible JNA native library installed on thi
- library object passed to getNativeLibrary in not a proxy
AI-assisted analysis of java-native-access/jna@d036ad9781 (2026-09-12).
Data as JSON: /api/errors/a4b0a9236f6abf53.
Report an issue: GitHub.