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

  1. 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.
  2. 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.
  3. Check that jvm pointer is valid and the library is loaded inside a running JVM, not after JNI_OnUnload.
  4. 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

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


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