➡️ Calling Native code from JVM
The @JNIConnect annotation is the core of the KSP module. It generates the necessary JNI boilerplate so you can write clean, idiomatic Kotlin code.
1. Write Your Kotlin/Native Function
Write your function using standard Kotlin types and annotate it with @JNIConnect.
import dev.datlag.nkommons.JNIConnect
@JNIConnect(
packageName = "your.package.name",
className = "YourClass",
// optional, defaults to function name (example).
functionName = "customFunction"
)
fun example(a: String, b: Boolean, c: CharArray, d: Double): String {
return "$a, $b, $c, $d"
}
2. Let KSP generate the JNI Stub
When you build the project, KSP will automatically generate a JNI-compatible function. You don't need to touch this generated file.
The following will be generated:
import dev.datlag.nkommons.JNIEnvVar
import dev.datlag.nkommons.binding.jboolean
import dev.datlag.nkommons.binding.jcharArray
import dev.datlag.nkommons.binding.jdouble
import dev.datlag.nkommons.binding.jobject
import dev.datlag.nkommons.binding.jstring
import dev.datlag.nkommons.utils.toJString
import dev.datlag.nkommons.utils.toKBoolean
import dev.datlag.nkommons.utils.toKCharArray
import dev.datlag.nkommons.utils.toKString
import kotlin.OptIn
import kotlin.experimental.ExperimentalNativeApi
import kotlin.native.CName
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.ExperimentalForeignApi
@OptIn(ExperimentalForeignApi::class, ExperimentalNativeApi::class)
@CName("Java_your_package_name_YourClass_customFunction")
public fun _exampleJNI(
env: CPointer<JNIEnvVar>,
clazz: jobject,
p0: jstring,
p1: jboolean,
p2: jcharArray,
p3: jdouble,
): jstring? {
return example(
p0.toKString(env) ?: return null,
p1.toKBoolean(),
p2.toKCharArray(env) ?: return null,
p3
).toJString(env)
}
3. Declare and Use in JVM
Now, you can declare and call the original function in your Java or JVM Kotlin code as if it were a simple external method.
package your.package.name
object YourClass {
// same signature as native
external fun customFunction(
a: String,
b: Boolean,
c: CharArray,
d: Double
): String
}
Supported Types
The following types are currently supported for direct mapping:
Type | ArrayType |
|---|
Boolean | BooleanArray |
Byte | ByteArray |
Char | CharArray |
Double | DoubleArray |
Float | FloatArray |
Int | IntArray |
Long | LongArray |
Short | ShortArray |
String | |
The following types will be mapped to a different Native-friendly type.
JVM | Native |
|---|
java.nio.ByteBuffer
| dev.datlag.nkommons.ByteBuffer
|
Using java.nio.ByteBuffer
You can pass direct ByteBuffer from JVM to Native easily:
// JVM / Android
external fun fillBuffer(buffer: java.nio.ByteBuffer): Boolean
fun main() {
val buffer = ByteBuffer.allocateDirect(100)
fillBuffer(buffer)
}
On native side it will be mapped to dev.datlag.nkommons.ByteBuffer. You can access the address of the buffer, as well as its size.
// Native
import dev.datlag.nkommons.ByteBuffer
@JNIConnect(
packageName = "org.example",
className = "MainKt"
)
fun fillBuffer(buffer: ByteBuffer): Boolean {
val bufferAddress: CPointer<ByteVar> = buffer.address
val bufferCapacity: Long = buffer.size
Random.nextBytes(100).usePinned { randomBytes ->
memcpy(buffer.address, randomBytes.addressOf(0), size.toULong())
}
return true
}
Last modified: 03 February 2026