As Android developers, we usually make apps that focus on normal things. Things like easy-to-use screens, getting data from the internet, and sharing pictures and videos. These tasks are pretty straightforward with Kotlin or Java, the main languages we use for Android.
But what if your app needs to do more complex stuff? Imagine building an app that needs to do hard math, process images, or understand speech and faces. Suddenly, it can feel really tough and confusing.
You’re not the only one who faces these challenges. Many Android developers, including me, run into situations where we need to solve tricky engineering problems like these. This is where Java Native Interface (JNI), using C++, comes to help. It opens up many new possibilities.
In this article, we’ll learn about JNI. We’ll see what it is, how it works with our Java and Kotlin code, and what kinds of basic data it can handle. We’ll also explain the Android Native Development Kit (NDK). We’ll show you how to create a simple Android app with an NDK library.
What is JNI? Bridging Java/Kotlin with Native Code
Sometimes Java or Kotlin alone isn’t enough — especially in system level apps, where you may need low-level hardware access or high-performance computation. That’s where JNI (Java Native Interface) comes in. It acts as a bridge between Java/Kotlin and native C/C++ code.
Java/Kotlin runs on the Android Runtime (ART), while native code runs directly on the hardware. JNI allows managed code to call native functions compiled into machine code — which are typically faster and more hardware-aware.
How It Works:
- Declare Native Methods: Use the native keyword to mark methods implemented in C/C++.
- Generate JNI Headers: Use tools like javac -h to generate C/C++ header files with method signatures. In Android Studio, this is straightforward using the JNI support.
- Write Native Code: Implement the methods in C/C++, using JNIEnv to work with Java objects and data.
- Compile to Shared Library: Build the native code into a .so shared library.
- Load the Library: Use System.loadLibrary() in your Java/Kotlin code to load the
.soat runtime.
When a native method is called, ART hands off execution to the corresponding C/C++ function and retrieves the result back to Java/Kotlin.
Why Use JNI?
- Performance Boost: Ideal for computation-heavy tasks like signal processing or ML inference in the vehicle.
- Reuse Existing Libraries: Leverage existing, well-tested C/C++ libraries in your Android OS
- Access Low-Level Features: Useful when working with HALs, ECUs, sensors, or proprietary native APIs.
- Cross-Platform Compatibility: Native libraries can work across devices, car models, and JVMs without changes.
JNI lets you combine the safety of Java/Kotlin with the speed and flexibility of native code — a powerful approach in Android operating systems.
What is Android NDK?
The Android Native Development Kit (NDK) is a set of tools that lets you embed native C/C++ code in Android applications. It allows performance-critical parts of your app to run outside the Android Runtime (ART), directly on the hardware — ideal for tasks like real-time processing, graphics rendering, or reusing existing native code.
While most Android development is done in Java or Kotlin, the NDK is especially useful in system level apps, where many system components and libraries are written in C/C++.
Why Use NDK in System apps?
- Most native libraries in the Android OS are compiled and shared as
.so(shared object) files, which your app or service can consume via the NDK.
For vendor-specific native libraries, the NDK has limited support. These are often not exposed through standard headers or prebuilt
.sofiles via ndk.
What Does the NDK Provide?
- Cross-Toolchains: for building native binaries (ARM, x86) on Linux, macOS, and Windows.
- System Headers & Stable Libraries: libc, libm, liblog, JNI, OpenCV, OpenGL ES,and more.
- C++ Standard Library and native activity APIs for direct input/sensor access.
- Build System (ndk-build / CMake): Simplifies compiling native sources and generating
.solibraries for inclusion in your APK or Android build. - Packaging Support: Bundles compiled native libraries directly into your
.apk. - Samples & Documentation to get started with native code integration.
When Should You Use the NDK?
- When your app needs to run fast and respond instantly — like for real-time audio, image processing, video rendering, machine learning, or sensor data processing.
- If you want to reuse existing C or C++ libraries, which is common in system apps where performance matter.
- When you’re working deep in the Service layer and need to talk to native system components — like HALs or services written in C++.
JNI in Practice
In this section, we’ll walk through a practical example of using JNI in an Android project. We’ll create a simple Android app using Android Studio, build a native C++ library, and set up the communication between the Java/Kotlin layer and our native code.
By the end, you’ll see how your app can call native functions using JNI and return results back to the Android layer.
Step 1: Create a New Android Studio Project
- Open Android Studio and click on “New Project”.
- Choose the “Empty Views activity” template (under “Phone and Tablet”)
- Name your project (e.g., CarJNIApplication) and select Java or Kotlin as your programming language.
- Set the Minimum SDK to a version that suits your target devices (API 24 or higher is generally safe).
- Click Finish and let Android Studio generate the project.

Step 2: Create a Native Library module
Now that your base project is ready, it’s time to create a separate native library module for better modularity.
- Go to File → New → New Module.
- Select “Android Native Library” from the list.
- Set the Module Name to nativemath.
- Choose C++ as the source language (leave other defaults as-is).
- Click Finish.

Once the nativemath module is created, Android Studio will automatically generate:
- nativemath.cpp – a C++ source file with a sample “Hello from C++” function.
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring
JNICALL
Java_com_droid_nativelib_NativeLib_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
- CMakeLists.txt – a CMake build script that tells the build system how to compile the native code.
cmake_minimum_required(VERSION 3.22.1)
project("nativelib")
add_library(${CMAKE_PROJECT_NAME} SHARED
nativemath.cpp)
target_link_libraries(${CMAKE_PROJECT_NAME}
android
log)
What is CMake?
CMake is a cross-platform build system used to compile C/C++ code. In Android projects, it works with the NDK to:
- Define which native .cpp or .c files to compile.
- Specify output names for shared libraries (e.g., libnativemath.so).
- Link required native libraries (e.g., log, android, etc.).
- Handle ABI configurations for different device architectures (e.g., armeabi-v7a, arm64-v8a).
Step 3: Include the Native Library Module in the App
To use your native library from the main app, you need to add it as a module dependency.
- Open your app-level build.gradle file (app/build.gradle.kts).
- Inside the dependencies block, add:
implementation(project(":nativemath"))
3. Sync the project to apply changes (File → Sync Project with Gradle Files).
Step 4: Call the Native Method in the App
Now that the nativemath library is added as a module dependency, it’s time to invoke the native method from your app.
You can call the function from the MainActivity and display the result in a TextView like this:
findViewById<TextView>(R.id.helloWorldText).text = NativeMathLib().stringFromJNI()
Step 5: Run the App
Now, build and run the app on a device or emulator.
You should see the native C++ message — typically like below:

Creating a Native Custom Method with JNI
Now let’s add a native method to perform addition using JNI. This demonstrates how to extend your native library with custom functionality and bind it seamlessly to your Kotlin code.
Step 1: Declare the Method in Kotlin
In your NativeMathLib class, add the following declaration.
external fun add(x: Int, y: Int): Int
This tells the Kotlin compiler that the add() method will be implemented natively in C++.
Step 2: Generate the JNI Method Stub
Once declared, Android Studio will highlight the method and prompt you to create the corresponding JNI method.
Use Alt + Enter → “Create JNI function” to automatically generate the native stub in nativemath.cpp.
Step 3: Implement the Native Logic
Android Studio generates a function like this:
extern "C" JNIEXPORT jint JNICALL
Java_com_droid_nativelib_NativeMathLib_add(JNIEnv * /* env */, jobject/* clazz */, jint x, jint y) {
LOGD("Native", "Adding %d + %d = %d", x, y, x + y);
return x + y;
}
You can now write any logic inside this function. In this case, we’re simply returning the sum of two integers.
Step 4: Calling the Custom Native Method
Now that your native add() method is implemented, you can easily call it from your app’s activity just like any regular Kotlin function.
For example, in your MainActivity, simply invoke:
val result = NativeMathLib().add(5, 6)
You can display the result in a TextView, log it, or use it however your app requires.
Job Offers
Conclusion:
JNI and the Android NDK open up powerful capabilities for Android developers. Whether it’s boosting performance, reusing existing C/C++ libraries, or accessing system-level features, integrating native code allows you to go beyond what’s possible with Java or Kotlin alone. With a clear setup and proper tooling in Android Studio, you can easily bridge your Kotlin code with native logic — combining the safety of managed code with the speed of native execution.
Thanks for reading! If this article was helpful, please follow me in medium and share this article on social media!!!
Follow me on Linkedin
Source Code:
Reference:
https://developer.android.com/ndk/guides
https://www3.ntu.edu.sg/home/ehchua/programming/java/javanativeinterface.html
This article was previously published on proandroiddev.com.



