java-native-access/jna · error · java.lang.IllegalArgumentException
Library must be a proxy class
Error message
Library must be a proxy class
What it means
Native.synchronizedLibrary wraps a Library proxy so all calls are serialized, but it only works on the dynamic proxy instances returned by Native.loadLibrary/Native.load. Passing any other Library implementation (hand-written class, different proxy) fails Proxy.isProxyClass and throws this IllegalArgumentException.
Source
Thrown at src/com/sun/jna/Native.java:1306
/** Set the OS last error code. The value will be saved on a per-thread
* basis.
*/
public static native void setLastError(int code);
/**
* 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);
}View on GitHub (pinned to d036ad9781)
Solutions
- Pass the exact object returned by Native.loadLibrary/Native.load (the JNA proxy) to synchronizedLibrary
- If you need a hand-written implementation, add your own synchronization instead of using synchronizedLibrary
- Create the library via Native.load(..., Library.class) first, then wrap: Native.synchronizedLibrary(lib)
- Check for wrapper layers (caching frameworks) that may have replaced the proxy with another object
Example fix
// before
MyLib impl = new MyLibImpl(); // hand-written
Library sync = Native.synchronizedLibrary(impl);
// after
MyLib lib = Native.load("mylib", MyLib.class);
MyLib sync = (MyLib) Native.synchronizedLibrary(lib); Defensive patterns
Strategy: type-guard
Validate before calling
static Library synchronizeIfProxy(Library lib) {
if (!Proxy.isProxyClass(lib.getClass())) {
throw new IllegalArgumentException("Pass the proxy returned by Native.load, not " + lib.getClass());
}
return (Library) Native.synchronizedLibrary(lib);
} Type guard
boolean isJnaLibraryProxy(Library lib) {
return Proxy.isProxyClass(lib.getClass())
&& Proxy.getInvocationHandler(lib) instanceof com.sun.jna.Library.Handler;
} Try / catch
try { return Native.synchronizedLibrary(lib); } catch (IllegalArgumentException e) { if (String.valueOf(e.getMessage()).equals("Library must be a proxy class")) { Library real = Native.load(libName, lib.getClass().asSubclass(Library.class)); return Native.synchronizedLibrary(real); } throw e; } Prevention
- Only wrap objects returned by Native.load/Native.loadLibrary
- Never pass hand-written or test-mock Library implementations to synchronizedLibrary
- Use the typeGuard above in utility code that accepts a Library
When it happens
Trigger: Calling Native.synchronizedLibrary(obj) where obj is a hand-rolled implementation of a Library interface, a subclass, a CGLIB/other proxy, or a library instance obtained from a different framework.
Common situations: Wrapping a mocked Library in tests; passing a manually implemented interface to add thread-safety; mixing libraries created by another wrapper utility; assuming synchronizedLibrary accepts any Library.
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
- Component must be heavyweight: " + w
- OPTIONS must be a public field of type java.util.Map (e): ma
- library object passed to getNativeLibrary in not a proxy
- FileMonitor not implemented for " + os
- No trash location found (define fileutils.trash to be the pa
AI-assisted analysis of java-native-access/jna@d036ad9781 (2026-09-12).
Data as JSON: /api/errors/61cc35c6b3ee0f97.
Report an issue: GitHub.