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.

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.

Appendix: 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)