Kotlin-JNI Help

⬅️ Calling JVM code from Native

Calling back is easy too!

1. Create a contract interface in commonMain

@JniCallback interface JvmCallback: AutoCloseable { fun sayHello(): String }

The @JniCallback annotation will tell KSP to generate relevant bindings on the native side.

package dev.datlag.nkommons import com.dshatz.kni.binding.jobject import com.dshatz.kni.utils.CallObjectMethodA import com.dshatz.kni.utils.FindClass import com.dshatz.kni.utils.GetMethodID import com.dshatz.kni.utils.toKString import kotlin.OptIn import kotlin.String import kotlinx.cinterop.CPointer import kotlinx.cinterop.ExperimentalForeignApi import kotlinx.cinterop.`get` import kotlinx.cinterop.allocArray import kotlinx.cinterop.memScoped public class _JvmCallbackNativeImpl( env: CPointer<JNIEnvVar>, instance: jobject, ) : BaseCallback(env, "dev/datlag/nkommons/JvmCallback", instance), JvmCallback { override fun sayHello(): String { val cls = jvmClass val methodId = env.GetMethodID(cls, "sayHello", "()Ljava/lang/String;")!! return memScoped { val args = allocArray<jvalue>(0) env.CallObjectMethodA(ref, methodId, args)?.toKString(env)!! } } }

2. Create the JVM implementation

// JVM / Android class JvmCallbackImpl: JvmCallback { override fun sayHello(): String = "Hello" override fun close() { // This will be automatically called when the // native side calls close(). // Clean up resources on the JVM side. } }

3. Pass your JVM object to Native

lateinit var jvmCallback: JvmCallback // commonMain @JniCall expect fun init(callback: JvmCallback) @JniCall expect fun dispose() // Native actual fun init(callback: JvmCallback) { // Save the object for later. jvmCallback = callback } actual fun dispose() { jvmCallback.dispose() }

4. Call sayHello() from native!

// Native fun getGreetings() { val message = jvmCallback.sayHello() memScoped { fprintf(stderr, "Message from JVM: %s\n", message.cstr.ptr) } // Greetings received, dispose of the object jvmCallback.dispose() }
Last modified: 23 April 2026