What does the @Throws annotation do when a Kotlin/Native function is called from Swift or Objective-C, and what happens to an exception that isn't listed?
answer
- Default: exported funcs look non-throwing → unlisted throw crashes
- @Throws routes listed types into NSError** / Swift throws
- Subclasses of listed types also bridge
- JVM: only Java interop signal, not enforcement
- KotlinException stored in NSError userInfo
basics
~10 s@Throws tells the Kotlin compiler which exceptions should be passed to Swift or Objective-C as errors instead of crashing. Any exception you don't list will crash the whole app.
solid answer
~40 sOn Kotlin/Native, all functions are exposed to Objective-C/Swift as if non-throwing. @Throws(SomeException::class, ...) marks a function so that the listed exception types (and their subclasses) are bridged into an Objective-C NSError** out-parameter, which Swift surfaces as a thrown Swift error you handle with try/catch. Exceptions whose type is NOT in the @Throws list propagate up to the language boundary and terminate the program (the runtime calls processUnhandledException / aborts), instead of being caught. This differs from the JVM, where unchecked exceptions simply unwind the stack and can be caught anywhere. So @Throws is about marshalling across the Kotlin↔ObjC boundary, not about JVM-style compile-time checked-exception enforcement.
code
kotlin · 8 lines@Throws(IllegalArgumentException::class)
fun divide(a: Int, b: Int): Int {
require(b != 0) { "b must not be zero" } // throws IllegalArgumentException -> bridges
return a / b
}
// In Swift:
// do { let r = try KotlinClass().divide(a: 10, b: 0) }
// catch { print(error) } // IllegalArgumentException arrives as Swift Errorgo deeper
Knows @Throws lets exceptions reach Swift as errors and that unlisted ones crash the app.
Explains the NSError** bridge, subclass matching, and the default non-throwing exposure.
Contrasts Native vs JVM semantics and reasons about which exceptions to list on the API surface.
Frames @Throws as a boundary-marshalling contract and sets team conventions for what is recoverable vs fatal across the KMP API.
## The problem @Throws solves When you compile Kotlin to a **Kotlin/Native** framework for iOS/macOS, your Kotlin code is exposed to **Objective-C** and **Swift** through a generated header. Objective-C/Swift do not have Kotlin/Java-style exceptions; they signal failures through an **`NSError**` out-parameter** (Objective-C) which Swift maps to its own `throws` / `try` mechanism. By default, the Kotlin/Native compiler generates every exported function as if it **cannot throw**. If a Kotlin exception then reaches the boundary, there is no channel to report it, so the runtime **terminates the process** (it routes the throwable through `processUnhandledException` and aborts). This is a hard crash, not a catchable error. ## What `@Throws` does `@Throws` (the Kotlin `kotlin.Throws` annotation) lists which exception classes are allowed to **cross the boundary**: ```kotlin @Throws(MyDomainException::class, IllegalArgumentException::class) fun parse(input: String): Result { ... } ``` For those listed types (and their **subclasses**), the generated Objective-C signature gains an `error:` parameter (`NSError**`), and the thrown Kotlin exception is wrapped into an `NSError` whose `kotlin.Exception` is stored under the `KotlinException` userInfo key. In **Swift** the method becomes `throws`, and you write `try`. ## Listed vs unlisted - **Listed (or subclass of a listed type):** bridged → becomes a Swift `Error` you can `catch`. - **NOT listed:** the runtime treats it as a programming error at the boundary and **crashes** the process. It is NOT silently swallowed and NOT delivered to Swift. ## Contrast with the JVM Kotlin on the JVM has **no checked exceptions**; `@Throws` there only emits a `throws` clause in bytecode for **Java interop**. On the JVM an unlisted exception still unwinds and can be caught anywhere. On Native, the semantics are stricter: only the declared set bridges; everything else is fatal at the boundary. So the same annotation means different things per target. ## Practical guidance - Put `@Throws` on the **public API surface** that Swift calls. - List the **business/recoverable** exceptions; let truly fatal ones crash. - Cancellation (`kotlinx.coroutines.CancellationException`) is a special case for suspend functions — see follow-ups.
- If a function throws a subclass of a type listed in @Throws, does it bridge?Yes. @Throws matching is by type assignability, so subclasses of any listed class also cross the boundary as NSError/Swift Error.
- Does @Throws change behavior when the same code runs on the JVM?On the JVM it only adds a `throws` clause to bytecode for Java callers; it has no effect on Kotlin callers and does not enforce checked exceptions.
@Throws is like declaring which packages a customs gate will inspect and forward; anything not on the list gets the whole shipment confiscated (process killed) at the border.
saying these in an interview costs you the question
- Saying unlisted exceptions are silently swallowed or ignored
- Claiming @Throws makes Kotlin exceptions checked at compile time
- Thinking it behaves identically on JVM and Native
- Believing only the exact listed class bridges, not subclasses
- Assuming Swift gets a crash report it can catch without @Throws