java-native-access/jna · warning

JNA: could not detach thread after callback init

Error message

JNA: could not detach thread after callback init

What it means

After initializing a callback group on a thread that was not previously attached to the JVM, JNA attaches the thread, does the work, then calls DetachCurrentThread. This message is printed to stderr when DetachCurrentThread returns non-zero, meaning the JVM refused to detach the native thread. It is a diagnostic: the callback work itself completed, but the thread remains attached to the VM, which can leak thread-local JNI resources.

Source

Thrown at native/dispatch.c:1180

    if (!(*env)->IsSameObject(env, cbobj, NULL)) {
      jobject argsobj = newJavaStructure(env, args, classAttachOptions);
      group = (*env)->CallStaticObjectMethod(env, classCallbackReference,
                                             MID_CallbackReference_initializeThread,
                                             cbobj, argsobj);
      if (group != NULL) {
        group = (*env)->NewWeakGlobalRef(env, group);
      }
      if (args->name != NULL) {
        // Make a copy, since the Java Structure which owns this native memory
        // will go out of scope and be available for GC
        args->name = STRDUP(args->name);
      }
    }
  }
  (*env)->PopLocalFrame(env, NULL);
  if (!attached) {
    if ((*jvm)->DetachCurrentThread(jvm) != 0) {
      fprintf(stderr, "JNA: could not detach thread after callback init\n");
    }
  }

  return group;
}

jclass
getNativeType(JNIEnv* env, jclass cls) {
  return (*env)->CallStaticObjectMethod(env, classNative,
                                        MID_Native_nativeType, cls);
}

jclass
getNativeTypeMapped(JNIEnv* env, jobject converter) {
  return (*env)->CallObjectMethod(env, converter,
                                          MID_FromNativeConverter_nativeType);
}

View on GitHub (pinned to d036ad9781)

Solutions

  1. Ensure the callback thread also runs Java code (or is a Java-launched thread) so the JVM manages its lifecycle and JNA never needs to attach/detach it.
  2. Check for other code holding JNI references on that thread and release them before the callback returns, since held references make detach fail.
  3. Verify the JVM is not shutting down while native callback init runs; serialize teardown after all callback activity completes.
  4. If detach failures are benign in your setup, redirect/inspect stderr to confirm this is the only symptom and monitor for thread leaks.

Example fix

// before: native thread that JNA must attach/detach itself
pthread_create(&tid, NULL, register_jna_callback, NULL);
// after: use a JVM thread (Executors) so attach/detach is never needed
ExecutorService pool = Executors.newSingleThreadExecutor();
pool.submit(() -> nativeLib.registerCallback(cb));
Defensive patterns

Strategy: fallback

Prevention

When it happens

Trigger: A callback is registered/initialized from a purely native thread (never seen by the JVM); AttachCurrentThread succeeds during init but the subsequent DetachCurrentThread call fails (e.g. the thread still holds JNI local/global references or the JVM is shutting down).

Common situations: Native libraries (C/Rust/Go) invoking JNA callback setup on their own thread pools; embedding JNA in a process where the JVM is being torn down while callbacks are still registered; unusual JVM implementations that refuse detachment while monitors are held.

Related errors


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