skip to content

Java's IOException is a checked exception. What happens in Kotlin when you call a Java method that declares `throws IOException`?

level: middleimportance: must knowfreq 70%

answer

  1. Kotlin has zero checked exceptions
  2. Java `throws IOException` → no try/catch forced in Kotlin
  3. Exception still thrown at runtime — handle if meaningful
  4. @Throws restores the throws clause for Java callers
  5. All exceptions behave like RuntimeException to the compiler

basics

~10 s

Nothing is forced. Kotlin has no checked exceptions, so the compiler does not make you write try/catch or declare the exception. You may catch it if you want, but you don't have to.

solid answer

~40 s

Kotlin does not enforce checked exceptions. A Java method's `throws IOException` clause imposes **no** compile-time obligation in Kotlin: you are not required to wrap the call in try/catch nor to declare anything. The exception is still thrown at runtime and can propagate up the stack like any unchecked exception, so you *should* still handle it where it matters — Kotlin just won't force you. This removes the boilerplate of rethrowing/wrapping checked exceptions through layers. The reverse concern: when Kotlin code is called *from* Java and you want a Java caller to be allowed (or required) to catch a checked type, annotate the Kotlin function with `@Throws(IOException::class)` so the `throws` clause appears in the generated bytecode; without it, Java cannot `catch (IOException e)` a Kotlin-thrown checked exception without a compile error.

code

kotlin · 7 lines
kotlin
// Calling Java that declares throws IOException — no try/catch required
fun loadConfig(path: String): String =
    java.io.File(path).readText()   // may throw IOException; compiler won't force handling

// Reverse: let Java callers catch a Kotlin-thrown checked exception
@Throws(java.io.IOException::class)
fun save(path: String, text: String) = java.io.File(path).writeText(text)

go deeper

for a junior

Knows Kotlin doesn't force try/catch for Java checked exceptions.

for a middle

Explains 'no checked exceptions', that it still throws at runtime, and that you should still handle real I/O.

for a senior

Adds @Throws for the Java-calls-Kotlin reverse direction and the rationale (lambda/functional friction) behind dropping checked exceptions.

for a principal

Weighs the API-design trade-offs (error modeling via sealed Result types vs exceptions) and interop contracts across a mixed Kotlin/Java codebase.

## Checked vs unchecked — the Java background In Java, exceptions that extend `Exception` but not `RuntimeException` are **checked**: the compiler forces you to either `catch` them or declare them in the method's `throws` clause. `IOException`, `SQLException`, and `InterruptedException` are classic examples. This is enforced at compile time. ## Kotlin's rule: no checked exceptions Kotlin **does not have checked exceptions at all**. Every exception is treated like a Java `RuntimeException` from the compiler's perspective. Consequently, when you call a Java method that declares `throws IOException`: - The compiler does **not** require a try/catch. - The compiler does **not** require you to re-declare the exception. - The exception is still **thrown at runtime** and propagates normally. ```kotlin import java.io.BufferedReader import java.io.FileReader fun readFirstLine(path: String): String? { // FileReader/BufferedReader.readLine() declare throws IOException in Java // Kotlin requires NO try/catch here. val reader = BufferedReader(FileReader(path)) return reader.use { it.readLine() } // `use` closes it, still no forced catch } ``` ## Why Kotlin made this choice Checked exceptions cause boilerplate: rethrowing or wrapping through layers, empty `catch` blocks, and leaky abstractions in functional code (lambdas can't transparently throw checked exceptions). Kotlin's designers concluded the costs outweigh the benefits, so they dropped the enforcement. You can — and for real I/O **should** — still catch what you can meaningfully handle: ```kotlin try { readFirstLine("data.txt") } catch (e: java.io.IOException) { // allowed, just not required log.warn("read failed", e) } ``` ## The reverse direction: @Throws Problem arises when **Java calls Kotlin**. If a Kotlin function throws a checked exception, the generated bytecode has no `throws` clause, so a Java caller writing `catch (IOException e)` gets *"exception is never thrown in the corresponding try block"*. Fix it with the **`@Throws`** annotation: ```kotlin @Throws(java.io.IOException::class) fun writeFile(path: String) { /* ... */ } ``` Now the compiled method declares `throws IOException`, so Java code can catch or declare it. `@Throws` is purely for Java interop; it changes nothing for Kotlin callers. ## Key keywords/APIs No checked exceptions; `try`/`catch`; `@Throws(...::class)` for Java interop; `use` (the closeable extension that still doesn't force a catch).

  • If Kotlin doesn't force handling, can an IOException still crash the program?
    Yes. It propagates up like any unchecked exception and will terminate the thread/program if never caught. Kotlin removes the compile check, not the runtime behavior.
  • When do you actually need @Throws?
    Only when Kotlin code is called from Java and the Java side must catch or declare a specific checked exception type, or for frameworks that inspect the throws clause (e.g. some JUnit/RMI cases).

Java's checked exceptions are a seatbelt alarm that won't let you drive; Kotlin removed the alarm — the seatbelt still exists, you just have to choose to buckle up.

saying these in an interview costs you the question

  • Saying Kotlin forces try/catch on Java checked exceptions
  • Claiming the exception is silently swallowed (it isn't — it propagates)
  • Thinking @Throws affects Kotlin callers' obligations
  • Believing Kotlin converts checked exceptions into something safe at runtime

context