skip to content

@Throws on Native

On Native, @Throws declares which exceptions may cross into Objective-C or Swift as an NSError; anything unlisted terminates the process. It is a much sharper contract than the same annotation on the JVM.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

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?

level: juniorimportance: must knowfreq 60%

answer

  1. Default: exported funcs look non-throwing → unlisted throw crashes
  2. @Throws routes listed types into NSError** / Swift throws
  3. Subclasses of listed types also bridge
  4. JVM: only Java interop signal, not enforcement
  5. 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 s

On 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
kotlin
@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 Error

go deeper

for a junior

Knows @Throws lets exceptions reach Swift as errors and that unlisted ones crash the app.

for a middle

Explains the NSError** bridge, subclass matching, and the default non-throwing exposure.

for a senior

Contrasts Native vs JVM semantics and reasons about which exceptions to list on the API surface.

for a principal

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

context

open as a page

How is a bridged Kotlin exception surfaced in Objective-C and Swift, and how do you recover the original Kotlin exception object on the Swift side?

level: middleimportance: should knowfreq 45%

basics

~10 s

Kotlin generates an extra error parameter in Objective-C and a throwing method in Swift. The original Kotlin exception is wrapped inside the NSError, so Swift can read it from the error's userInfo.

open as a page

Compare @Throws semantics on Kotlin/Native versus the JVM. Why can the same annotation behave so differently?

level: middleimportance: should knowfreq 40%

basics

~10 s

On the JVM, @Throws only adds a note for Java callers and changes nothing for Kotlin. On Native, it actually controls which exceptions can safely cross into Swift; unlisted ones crash.

open as a page

When exposing a Kotlin suspend function to Swift via @Throws, how should CancellationException be handled, and why is it special?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Suspend functions become Swift async calls with a completion that can deliver an error. You should mark them so cancellation can be reported to Swift; otherwise a cancelled coroutine could crash instead of telling Swift it was cancelled.

open as a page

You own a KMP library consumed by an iOS app. What is your strategy for using @Throws across the public API to avoid crashes while keeping the Swift error model usable?

level: principalimportance: nice to knowfreq 22%

basics

~20 s

Decide which failures are recoverable and list those in @Throws on the public functions Swift calls, so they become catchable errors. Leave only truly fatal bugs unlisted, and give Swift a small, predictable set of error types.

open as a page