java-native-access/jna · critical

JNA: Can't attach native thread to VM for callback: %d (chec

Error message

JNA: Can't attach native thread to VM for callback: %d (check stacksize for callbacks)

What it means

dispatch_callback must attach a native (non-Java) thread to the JVM before it can invoke the Java callback. If AttachCurrentThread fails, JNA frees the thread-name/group resources, prints this message including the JNI status code, and silently returns — the Java callback is never invoked and the native call proceeds without its result.

Source

Thrown at native/callback.c:721

      options.detach = JNI_TRUE; // default detach behavior
      options.name = NULL;
      args.group = initializeThread(cb, &options);
      daemon = options.daemon ? JNI_TRUE : JNI_FALSE;
      needs_detach = options.detach ? JNI_TRUE : JNI_FALSE;
      args.name = options.name;
    }
    if (daemon) {
      attach_status = (*jvm)->AttachCurrentThreadAsDaemon(jvm, (void*)&env, &args);
    }
    else {
      attach_status = (*jvm)->AttachCurrentThread(jvm, (void *)&env, &args);
    }
    if (attach_status != JNI_OK) {
      free((void *)args.name);
      if (args.group) {
        (*env)->DeleteWeakGlobalRef(env, args.group);
      }
      fprintf(stderr, "JNA: Can't attach native thread to VM for callback: %d (check stacksize for callbacks)\n", attach_status);
      return;
    }
    tls = get_thread_storage(env);
    if (tls) {
      snprintf(tls->name, sizeof(tls->name), "%s", args.name ? args.name : "<unconfigured native thread>");
      tls->needs_detach = needs_detach;
      tls->jvm_thread = JNI_FALSE;
    }
    // Dispose of allocated memory
    free((void *)args.name);
    if (args.group) {
      (*env)->DeleteWeakGlobalRef(env, args.group);
    }
  }

  if (!tls) {
    fprintf(stderr, "JNA: couldn't obtain thread-local storage\n");
    return;

View on GitHub (pinned to d036ad9781)

Solutions

  1. Increase the native thread's stack size when creating it (e.g. pthread_attr_setstacksize / CreateThread dwStackSize), or raise -Xss.
  2. Verify the JVM is still live and not shutting down when the native thread calls back.
  3. Ensure jvm version passed to GetEnv/AttachCurrentThread matches (JNI_VERSION_1_4) and the JVM pointer is valid.
  4. Reduce native stack depth before invoking the callback (invoke on a shallower stack).

Example fix

// before
pthread_create(&tid, NULL, native_worker, ctx); // 64KB stack, too small
// after
pthread_attr_t a; pthread_attr_init(&a);
pthread_attr_setstacksize(&a, 1 << 20); // >= 1MB
pthread_create(&tid, &a, native_worker, ctx);
Defensive patterns

Strategy: validation

Validate before calling

// Before registering a callback on a native thread, ensure adequate stack:
// pthread: pthread_attr_setstacksize(&attr, 1<<20)
// Windows: CreateThread(NULL, 1<<20, ...)
// and confirm the JVM is up: JNIEnv via GetEnv != JNI_EDETACHED failure path

Prevention

When it happens

Trigger: A callback invoked on a native thread with insufficient stack space (JNI cannot create the attachment bookkeeping), or the JVM refusing attachment (shutting down, wrong JNI version), yielding attach_status != JNI_OK.

Common situations: Callbacks fired on deep native call stacks or tiny-stack threads (common on Windows with small default thread stacks, hence the 'check stacksize' hint), attaching during JVM shutdown, more attached threads than -Xss/native limits allow.

Related errors


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