DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Foreign Function and Memory API: Modernizing Native Interfacing in Java 17
  • Taming the Virtual Threads: Embracing Concurrency With Pitfall Avoidance
  • Java 21 SequenceCollection: Unleash the Power of Ordered Collections
  • Cordova: Communicating Between JavaScript and Java

Trending

  • Ten Years of Beam: From Google's Dataflow Paper to 4 Trillion Events at LinkedIn
  • Liquibase: Database Change Management and Automated Deployments
  • DuckDB for Python Developers
  • Architecting Sub-Microsecond HFT Systems With C++ and Zero-Copy IPC
  1. DZone
  2. Coding
  3. Java
  4. Diving into JNI: My Messy Adventures With C++ in Android

Diving into JNI: My Messy Adventures With C++ in Android

JNI is powerful but tricky. Automate boilerplate with generators, carefully manage references, test with CheckJNI, and embrace the chaos; it gets satisfying.

By 
Ruslan Vidzert user avatar
Ruslan Vidzert
·
Oct. 10, 25 · Analysis
Likes (3)
Comment
Save
Tweet
Share
3.3K Views

Join the DZone community and get the full member experience.

Join For Free

So, I've been deep in the trenches with JNI lately (yeah, that Java Native Interface stuff) while working on a project where we had to plug a C++ AI assistant into our Android app. At first, it felt like stepping into a weird twilight zone — half Java, half C++, and all these random edge cases you never think about until you hit them. I remember staring at the stack trace for what felt like hours, realizing that one tiny missed DeleteLocalRef was enough to crash the whole app. Thought I'd share what actually tripped me up, what worked, and some ways to make life a little less miserable if you ever have to do this.

What the Hell Is JNI Anyway?

JNI is basically the bridge that lets Java (or Kotlin) talk to C/C++ code and vice versa. On Android, it’s the only real way to get heavy lifting done efficiently or access low-level APIs that Java/Kotlin just can't reach. Honestly, the first time I tried to wrap my head around it, I felt like I was learning a new language on top of Java and C++ at the same time.

Some key points I painfully learned:

  • You can call C++ from Java, and Java from C++. Sounds simple, but the devil is in the details — especially when managing references and lifetimes.
  • Despite newer stuff like Project Panama or JNA, Android devs still live and die by JNI. Trying to convince anyone on my team to switch to newer abstractions was a losing battle.

The classic workflow:

  • Declare a native (Java) or external (Kotlin) method
  • Generate a .h header file with javac -h
  • Implement your C++ function using JNIEnv
  • Build with CMake / ndk-build and load the .so in Gradle

Example in Kotlin:

Kotlin
 
object Greeting {
   init {
       System.loadLibrary("libgreeting")
   }
   private external fun sayHello(name: String)
}


And Java:

Java
 
public class Greeting {
   static { System.loadLibrary("greeting"); }
   private static native void sayHello(@NonNull String name);
}


Building JNI projects

Options I've used:

  • CMake: standard, integrates nicely with Gradle
  • ndk-build: old-school, works fine for legacy stuff
  • External build: if your C++ is huge/messy, let a separate build pipeline spit out .so files

Early on, I tried mixing Gradle and an external build manually, and the first APK I got wouldn’t even load. Spent a day chasing UnsatisfiedLinkError before realizing the paths were wrong. Wrapping the external build in a Gradle plugin finally made everything consistent. That felt like the first small victory in this jungle. My initial structure:

Project structure

Boilerplate Hell and Generators

JNI is boring as hell if you’re writing everything by hand: method stubs, reference handling, glue code... it stacks up. I remember writing a single interface manually and spending a full morning just fixing subtle bugs in jstring conversions. My hairline was receding faster than the app was building.

Some popular generators:

  • Djinni (Dropbox, cross-platform, kinda dead since 2020)
  • SWIG (classic wrapper generator for tons of languages)
  • JNI Zero (Chromium, annotation-based, modern, but tied to Chromium build)

In my case, we adapted a JNI Zero-style generator into Gradle. Watching the generator spit out working JNI stubs felt like magic after days of repetitive coding. Saved a ton of hours and prevented countless silly bugs.

My Example: An AI Assistant in an Android App

Imagine integrating a C++ AI assistant into an app. Instead of rewriting logic in Java/Kotlin, you just reuse the same C++ code everywhere. JNI is unavoidable.

Scale:

  • ~5k lines of C++
  • ~50k lines of Java/Kotlin
  • Generator produces JNI wrappers automatically

The flow:

  • In Kotlin, create listener with @JNINamespace + native methods
  • Annotate methods called from C++ with @CalledByNative
  • Generator spits out simplified JNI stubs
  • Implement listener in C++ using the generated headers
  • Initialize listener in C++ so Java UI gets updates

Seeing the generator work felt like watching the pieces of a puzzle finally click into place. Suddenly, instead of worrying about glue code, we could focus on features. It was surprisingly satisfying to see the C++ assistant pop up in the UI without crashes.

CI/CD and Gradle Tricks

Integrating C++ into a proper Android build pipeline was a lesson in patience:

  • Wrap external builds in Gradle tasks so the pipeline is aware of native dependencies
  • Output .so files to jniLibs folders for automatic packaging
  • Use variant-aware tasks for debug/release flavors to avoid mismatched builds
  • Cache compiled .so artifacts in CI to cut build times drastically
  • Run small instrumentation tests after every build to catch subtle JNI errors early

I remember one morning when a CI build failed due to mismatched .so files. It took me three cups of coffee and a couple of “why is this happening” mutterings to fix it. After wrapping everything neatly, the pipeline became almost boringly reliable.

Gotchas

  • References: Always clean up local references. Local Reference Table overflow is silent until your app crashes randomly.
  • Compiler/linker quirks: If the compiler thinks a JNI function isn't used, it strips it. Workaround: call a dummy noinline function in JNI_OnLoad.
  • CheckJNI mode: Run in emulator or rooted device. Catches thread misuse, invalid references, UTF issues, null pointers... basically all the weird stuff that makes you pull your hair out.
  • Unit tests: Instrumentation tests covering the JNI layer are lifesavers. I remember one subtle bug that caused a crash only on Pixel devices – it took tests like that to catch it before users did.

TL;DR

JNI is tricky but powerful. If you:

  • Automate repetitive code with generators
  • Carefully manage references
  • Test with CheckJNI and instrumented tests

... you'll avoid most pain.

Even an imaginary AI assistant shows how you can scale JNI: fast updates, unified C++ code, good performance, and way less boilerplate headaches.

Honestly, after fighting through the initial chaos, JNI starts to feel... kinda satisfying — like taming a beast. Some days I even miss the weird thrill of debugging a random crash caused by a missing reference. It’s like a mix of fear, frustration, and eventual victory that only JNI can deliver.

Java Native Interface Android (robot) Java (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • Foreign Function and Memory API: Modernizing Native Interfacing in Java 17
  • Taming the Virtual Threads: Embracing Concurrency With Pitfall Avoidance
  • Java 21 SequenceCollection: Unleash the Power of Ordered Collections
  • Cordova: Communicating Between JavaScript and Java

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook