# JNIgen

> Learn how to use JNIgen to generate type-safe Dart bindings for Java and Kotlin APIs.



## Overview

JNIgen (Java Native Interface generator) is a code generator tool that creates
type-safe Dart bindings for Java and Kotlin libraries and APIs.
This allows calling Android APIs directly and synchronously from your
Dart code.

## When to use JNIgen

Use JNIgen when you need to call Java or Kotlin libraries, wrap compiled JAR
or AAR files, or interact directly with Android from your Dart code.

JNIgen uses direct JNI bindings through `dart:ffi`,
making it the recommended tool when you require high-performance,
low-latency interop, or need to pass massive amounts of raw data between Dart
and the JVM.

For standard, cross-platform plugin development where you don't have
critical data transfers, use [Pigeon](/interop/pigeon)
instead.

## Developer flow

Generate bindings with JNIgen using the following general flow:

1.  Reference the native classes or JARs.
1.  Write the JNIgen configuration script.
1.  Generate the bindings by running JNIgen.
1.  Call the generated code from Dart.

:::note
For a complete list of configuration options, consult the
[JNIgen documentation on pub.dev][jnigen-pub] and the JNIgen README.
:::

To start, identify the native Java classes, packages, or compiled JAR/AAR files
you want to wrap.
Then write a configuration script (typically `tool/jnigen.dart`).
This configuration specifies where the native files are located,
what packages to include, and where to output the generated Dart code.

Next, run the configuration script (`dart run tool/jnigen.dart`),
which parses the classes and generates the corresponding Dart bindings.

Last, import the generated Dart file into your app and call the functions
directly.
JNIgen communicates directly through the underlying FFI layer,
so refer to the [`dart:ffi` guide](/interop/dart-ffi)
to learn how to manage native memory lifetimes and execute native calls
without blocking the UI thread.

:::note
If the native API changes, update your configuration script and
regenerate the bindings.
:::

## Examples

To explore complete implementations of this flow,
including how to configure the generator,
refer to the [`permissions_plugin` demo][permissions-demo] and the
[`android_toast_demo`][toast-demo] in the official Flutter native interop
demos repository.

For a production package on pub.dev that uses JNIgen to wrap the Android
Cronet HTTP library, check out [`package:cronet_http`][cronet-http-pub]
([source on GitHub][cronet-http-src]).

## Constraints and limitations

Keep these general considerations and limitations in mind when working with
JNIgen:

### Threading considerations

JNIgen calls run synchronously on the calling thread.
Watch out for two common scenarios:

- When calling platform APIs that must run on the platform thread,
  use [`runOnPlatformThread`][run-on-platform-thread] from `dart:ui`.
- Dart callbacks from Java run on the isolate that created them.
  This can lead to deadlocks if a blocked Java thread waits for a blocked
  Dart thread.

To learn more, consult the [JNIgen threading guide][jnigen-threading-doc].

### Lifecycle of Java objects

JNI wrappers use Java global references.
There is a hard limit of 50,000 active references.
Call `.release()` or use `.using()` blocks to prevent memory leaks.
Use `WeakReference` to break reference cycles between Dart and Java GCs
when implementing interfaces, as shown in the
[JNI object lifecycle guide][jnigen-lifecycle-doc].

### JVM runtime type checks

JNI wrappers are Dart extension types that compile down to `JObject`
at runtime, so Dart's native `is` and `as` keywords don't check the Java
runtime type.
Use `x.isA(Foo.type)` and `x.as(Foo.type)` instead, as shown in the
[Java runtime types guide][jnigen-runtime-types-doc].

### General debugging

Tracking down JNI memory allocation bugs like `DoubleReleaseError`
and `UseAfterReleaseError` can be difficult.
Set `Jni.captureStackTraceOnRelease = true` to log stack traces
and help trace leaks, as explained in the
[JNIgen debugging guide][jnigen-debugging-doc].

## Resources

To learn about generating Java and Kotlin bindings with JNIgen,
refer to the following resources:

- [JNIgen package on pub.dev][jnigen-pub]
- [JNI package on pub.dev][jni-pub]
- [Official JNIgen examples on GitHub][jnigen-examples]

[jnigen-pub]: https://pub.dev/packages/jnigen
[jni-pub]: https://pub.dev/packages/jni
[cronet-http-pub]: https://pub.dev/packages/cronet_http
[cronet-http-src]: https://github.com/dart-lang/http/tree/master/pkgs/cronet_http
[jnigen-examples]: https://github.com/dart-lang/native/tree/main/pkgs/jnigen/example
[permissions-demo]: https://github.com/flutter/demos/tree/main/native_interop_demos/permissions_plugin
[toast-demo]: https://github.com/flutter/demos/tree/main/native_interop_demos/android_toast_demo
[run-on-platform-thread]: https://api.flutter.dev/flutter/dart-ui/runOnPlatformThread.html
[jnigen-threading-doc]: https://github.com/dart-lang/native/blob/main/pkgs/jnigen/doc/threading.md
[jnigen-lifecycle-doc]: https://github.com/dart-lang/native/blob/main/pkgs/jnigen/doc/lifecycle.md
[jnigen-runtime-types-doc]: https://github.com/dart-lang/native/blob/main/pkgs/jnigen/doc/java_runtime_types.md
[jnigen-debugging-doc]: https://github.com/dart-lang/native/blob/main/pkgs/jnigen/doc/debugging.md

