java-native-access/jna · error
JNA: Can't attach native thread to VM for closure handler
Error message
JNA: Can't attach native thread to VM for closure handler
What it means
When a native callback (closure) fires on a thread the JVM has never seen, JNA tries AttachCurrentThread to obtain a JNIEnv. If attachment fails, it prints this message to stderr and aborts the callback invocation entirely — the Java callback method is never called and the native call site sees no invocation.
Source
Thrown at native/dispatch.c:2065
}
if (throw_type) {
throwByName(env, throw_type, throw_msg);
}
}
static void
closure_handler(ffi_cif* cif, void* resp, void** argp, void *cdata)
{
callback* cb = (callback *)cdata;
JavaVM* jvm = cb->vm;
JNIEnv* env;
jobject obj;
int attached = (*jvm)->GetEnv(jvm, (void *)&env, JNI_VERSION_1_4) == JNI_OK;
if (!attached) {
if ((*jvm)->AttachCurrentThread(jvm, (void *)&env, NULL) != JNI_OK) {
fprintf(stderr, "JNA: Can't attach native thread to VM for closure handler\n");
return;
}
}
// Give the callback its own local frame to ensure all local references
// are properly disposed
if ((*env)->PushLocalFrame(env, 16) < 0) {
fprintf(stderr, "JNA: Out of memory: Can't allocate local frame");
}
else {
obj = (*env)->NewLocalRef(env, cb->object);
if ((*env)->IsSameObject(env, obj, NULL)) {
fprintf(stderr, "JNA: callback object has been garbage collected\n");
if (cif->rtype->type != FFI_TYPE_VOID)
memset(resp, 0, cif->rtype->size);
}
else {
(*env)->CallVoidMethod(env, obj, MID_ffi_callback_invoke,View on GitHub (pinned to d036ad9781)
Solutions
- Ensure the JVM stays alive for the entire lifetime of the native code that can invoke callbacks; join/stop native threads before System.exit or library unload.
- Route the callback through a Java-created thread (have Java own the event loop and poll/queue events) so AttachCurrentThread is never required on foreign threads.
- Check that jvm pointer is valid and the library is loaded inside a running JVM, not after JNI_OnUnload.
- Capture stderr around reproduction to confirm the failure happens only at shutdown, and defer callback unregistration until after native threads stop.
Example fix
// before: unregister callback while native worker still runs runtime.exit(0); // JVM dies, in-flight callback attach fails // after: stop native work first nativeLib.stopWorker(); workerThread.join(5000); runtime.exit(0);
Defensive patterns
Strategy: validation
Validate before calling
// Before enabling native-thread callbacks, verify the JVM outlives them:
if (!jvmHealthy || shutdownInProgress) {
throw new IllegalStateException("Refusing to register native callback: JVM not guaranteed alive");
} Prevention
- Never let native threads outlive the JVM: stop and join them before shutdown hooks run.
- Prefer queue-based event delivery over direct native-to-Java callbacks on foreign threads.
- Test callback paths under rapid start/stop cycles to catch attach failures early.
When it happens
Trigger: A native library invokes a JNA Callback from a native thread (e.g. C thread pool, signal handler, OS timer) while AttachCurrentThread returns != JNI_OK, typically because the JVM is being destroyed or the thread cannot be registered.
Common situations: Callbacks fired during JVM shutdown; native audio/network/device driver threads calling back into Java; daemon native threads outliving the JVM; embedding JNA in a host app where the VM was destroyed before the native library finished.
Related errors
- JNA: could not detach thread after callback init
- JNA: could not detach thread after callback handling
- JNA: Can't attach native thread to VM on load
- JNA: Out of memory: Can't allocate local frame
- JNA: callback object has been garbage collected
AI-assisted analysis of java-native-access/jna@d036ad9781 (2026-09-12).
Data as JSON: /api/errors/badf6f6197877346.
Report an issue: GitHub.