Compare @Throws semantics on Kotlin/Native versus the JVM. Why can the same annotation behave so differently?
answer
- JVM: throws-clause for Java callers only, inert for Kotlin
- Native: load-bearing, opens the NSError channel
- Native unlisted → process abort; JVM unlisted → still catchable
- JVM shares exception runtime; ObjC/Swift does not
- Kotlin never gets compile-time checked exceptions
basics
~10 sOn 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.
solid answer
~40 s`@Throws` is one annotation with target-specific compiler behavior. On the **JVM**, Kotlin has no checked exceptions, so `@Throws` only emits a `throws` clause into the method's bytecode signature so **Java** callers see a checked exception; it imposes no requirement on Kotlin callers and does not change runtime unwinding — any exception can still be caught anywhere. On **Kotlin/Native**, exported functions are non-throwing by default, so `@Throws` is **load-bearing**: it generates the `NSError**` channel and declares exactly which exception types may bridge to Objective-C/Swift. Listed types bridge; unlisted ones hit the boundary as unhandled and **terminate the process**. The difference exists because the JVM already has a universal exception model shared by Kotlin/Java, whereas the ObjC/Swift boundary has none, so the annotation must do real marshalling work there.
go deeper
Knows @Throws matters more on iOS/Native than on Android/JVM.
Explains the JVM Java-interop throws clause versus the Native NSError marshalling, and why unlisted differs.
Reasons about a shared commonMain API where the same annotation has benign JVM and safety-critical Native effects.
Sets multiplatform API conventions so iOS safety is guaranteed without burdening the JVM target or misusing the annotation as type enforcement.
## Same annotation, two jobs `kotlin.Throws` is multiplatform, but the compiler backend for each target interprets it differently. ### On the JVM - Kotlin **does not have checked exceptions**. You can throw anything without declaring it. - `@Throws(IOException::class)` exists purely for **Java interop**: it writes a `throws IOException` clause into the compiled method signature so Java callers are forced (by the Java compiler) to handle or declare it. - For **Kotlin callers** it is inert — no compile-time check, no runtime change. Unwinding is the normal JVM model: any `throw` propagates and is catchable anywhere up the stack. ### On Kotlin/Native - Functions exported to Objective-C/Swift are generated as **non-throwing** by default. - `@Throws` is the **only** way to open an error channel: it emits the `error:(NSError**)` parameter and declares the bridgeable set. - **Listed** exception types (and subclasses) → wrapped into `NSError` → Swift `throws`. - **Unlisted** exception types reaching the boundary → `processUnhandledException` → **process abort**. ## Why the divergence The JVM gives Kotlin and Java a **shared exception runtime**, so nothing special is needed to cross between them — the annotation is just documentation/interop sugar. The **ObjC/Swift** boundary has **no exception runtime in common** with Kotlin; failures there must be reified into `NSError`. So on Native the annotation has to actually decide and implement the marshalling contract. ## Practical consequences - A multiplatform `commonMain` function with `@Throws` is harmless on JVM/Android but **safety-critical** on iOS — it determines crash-vs-recoverable behavior. - Putting `@Throws` only where Swift consumes the API keeps the iOS surface safe without affecting Android. ```kotlin // commonMain @Throws(ApiException::class) fun fetch(id: String): Dto // Android: just a throws-clause for any Java callers. // iOS: ApiException bridges to Swift; anything else crashes. ``` ## Edge note `@Throws` does not make exceptions **checked** for Kotlin anywhere; Kotlin remains uncheck-exception throughout. It only affects the cross-language signature/marshalling.
- Does @Throws ever make Kotlin enforce checked exceptions at compile time?No. Kotlin has no checked exceptions on any target. @Throws only affects interop signatures (JVM->Java) and Native boundary marshalling.
- Is a @Throws in commonMain dangerous for the Android build?No. On Android/JVM it is inert for Kotlin callers and only adds a Java-visible throws clause; the crash-vs-bridge semantics apply only on Native.
saying these in an interview costs you the question
- Saying @Throws enforces checked exceptions in Kotlin
- Claiming behavior is identical on JVM and Native
- Thinking the JVM aborts on an unlisted exception like Native does
- Believing @Throws on JVM affects Kotlin-to-Kotlin calls
- Not knowing the JVM clause is for Java interop