skip to content

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%

answer

  1. Selector gains error:(NSError**) trailing param
  2. NSError domain = KotlinException
  3. Original throwable in userInfo["KotlinException"]
  4. Swift: method is `throws`, use try + do/catch
  5. Downcast kotlinException to a typed catch

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.

solid answer

~40 s

For a function marked `@Throws`, the Kotlin/Native compiler appends an `error:(NSError**)` out-parameter to the generated Objective-C selector. When a listed exception is thrown, the runtime allocates an `NSError` with domain `"KotlinException"` and stores the actual `KotlinBase`/`Kotlin…Exception` instance under the userInfo key `"KotlinException"` (also surfaced as the typed `kotlinException` property in newer interop). In Swift the method is imported as `throws`, so you call it with `try` inside `do/catch`; the caught value is a Swift `Error`/`NSError`. To inspect the original Kotlin exception you bridge back via `(error as NSError).userInfo["KotlinException"]` (or the generated `kotlinException` accessor) and downcast to the Kotlin type. The message is also exposed via `localizedDescription`. This lets Swift do typed `catch` against Kotlin exception subclasses.

code

kotlin · 12 lines
kotlin
class MyDomainException(val code: Int, message: String) : Exception(message)

@Throws(MyDomainException::class)
fun load(id: String): Record {
    if (id.isBlank()) throw MyDomainException(400, "blank id")
    return fetch(id)
}
// Swift side:
// do { let r = try api.load(id: "") }
// catch let e as NSError {
//   if let dom = e.kotlinException as? MyDomainException { print(dom.code) }
// }

go deeper

for a junior

Knows the exception shows up in Swift as an error you catch with do/catch.

for a middle

Describes the NSError**/error: parameter, the KotlinException domain, and recovering the typed Kotlin object from userInfo.

for a senior

Designs Swift-side typed catch flows and keeps version-safe access via the kotlinException accessor.

for a principal

Standardizes a cross-language error model so Swift and Kotlin teams share consistent typed recovery and avoid lossy string-only handling.

## The generated shapes ### Objective-C A `@Throws`-annotated Kotlin function gets an extra trailing parameter in its selector: ```objc - (BOOL)parseInput:(NSString *)input error:(NSError **)error; ``` If the Kotlin call throws a **listed** exception, the runtime sets `*error` to a freshly built `NSError` and the method returns a failure sentinel (e.g. `NO`/`nil`). ### The NSError contents - **domain**: `"KotlinException"`. - **userInfo["KotlinException"]**: the **actual Kotlin throwable** (a `KotlinBase` subclass), so no information is lost. - **localizedDescription / userInfo**: carries the message string. Newer Kotlin/Native interop also exposes a typed convenience: the `NSError` has a `kotlinException` property you can read directly instead of digging into `userInfo`. ### Swift Swift imports the throwing method as `throws`: ```swift do { let result = try parser.parse(input: raw) } catch let error as NSError { if let kt = error.kotlinException as? MyDomainException { handle(kt.code) } } ``` ## Why this matters - **Typed recovery**: because the real Kotlin object survives inside `userInfo`, Swift can `as?`-downcast to specific exception subclasses and branch on Kotlin fields/properties. - **Message access**: `error.localizedDescription` gives the Kotlin `message`. ## Common pitfalls - Treating the Swift `Error` as opaque and only logging `localizedDescription`, losing the typed Kotlin payload. - Forgetting that only **listed** types reach this path; others crash before any `NSError` is built. - Assuming the userInfo key name; it is the literal `"KotlinException"` (prefer the generated `kotlinException` accessor to stay version-safe). ## Relationship to @Throws The bridge **only exists** because of `@Throws`. Without it there is no `error:` parameter generated at all, so there is no channel — the throw is fatal.

  • What is the NSError domain for a bridged Kotlin exception?
    "KotlinException". The original throwable is stored in userInfo under the "KotlinException" key (also exposed via the kotlinException property).
  • How does Swift do a typed catch on a specific Kotlin exception subclass?
    Catch the NSError, read its kotlinException (or userInfo["KotlinException"]), and use `as?` to downcast to the specific Kotlin exception class.

saying these in an interview costs you the question

  • Saying the Kotlin object is discarded and only a string remains
  • Inventing a different NSError domain name
  • Claiming Swift gets a native Kotlin try/catch with no NSError involved
  • Forgetting the generated error: out-parameter on the ObjC selector
  • Assuming every exception type produces an NSError, even unlisted ones

context