Open Source


Whenever a @Knee callable throws, the exception is thrown on the other side of the bridge.

kotlin logokotlin
// Kotlin/Native @Knee fun throwSomething() { error("Something went wrong") } // Kotlin/JVM val failure = runCatching { throwSomething() }.exceptionOrNull() checkNotNull(failure) check(failure.message == "Something went wrong")


By default, exceptions are not serializable and can't pass the JNI bridge (a jthrowable is not a Throwable!). However, Knee strives to represents exception in the most transparent way, by reconstructing them with appropriate type and parameters.

Message preservation

Whenever possible, the exception message is preserved, as can be seen in the example above.

Type preservation

Some common types are preserved. For example, a kotlin.coroutines.cancellation.CancellationException in the backend will be re-thrown as a java.util.concurrent.CancellationException in the frontend.

Instance preservation

In some occasions, especially when lambdas are involved, the same exception can cross the JNI interface twice. Consider the following example:

kotlin logokotlin
// Native @KneeInterface typealias StringMapper = (String) -> String @Knee fun mapString(source: String, mapper: StringMapper): String { return mapper(source) }

The JVM consumer code may try to mapString, but throw an exception:

kotlin logokotlin
mapString("Hello") { throw IllegalStateException("Something went wrong") }

In this scenario, the IllegalStateException is thrown from the JVM, rethrown on the K/N side when invoking mapper, and finally rethrown on the JVM side when mapString returns. That exception will be exactly the same instance, meaning that you could check they're the same with ===.

While this may seem useless, many commonly used functions (notably, Flow.collectWhile) rely on these checks and can only work when such mechanism is in place.

Custom exceptions

You may also use custom exceptions, as long as they were properly annotated as classes.

kotlin logokotlin
@KneeClass class CustomException @Knee constructor(message: String) : RuntimeException(message) { @Knee override val message: String? get() = super.message }

Whenever a CustomException is thrown inside a Knee invocation, the runtime serializes it and creates a copy for the other side of the JNI interface! You can also annotate other functions or properties for them to be exposed to JVM.