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 instead.
Developer flow
#Generate bindings with JNIgen using the following general flow:
- Reference the native classes or JARs.
- Write the JNIgen configuration script.
- Generate the bindings by running JNIgen.
- Call the generated code from Dart.
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
to learn how to manage native memory lifetimes and execute native calls
without blocking the UI thread.
Examples
#
To explore complete implementations of this flow,
including how to configure the generator,
refer to the permissions_plugin demo
and the
android_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
(source on GitHub).
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
runOnPlatformThreadfromdart: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.
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.
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.
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.
Resources
#To learn about generating Java and Kotlin bindings with JNIgen, refer to the following resources:
Unless stated otherwise, the documentation on this site reflects Flutter 3.47. Page last updated on 2026-11-03. View source or report an issue.