skip to content

A Kotlin function throws a checked Java exception. Why might a Java caller fail to catch it, and how does @Throws fix this?

level: seniorimportance: should knowfreq 45%

answer

  1. Default Kotlin bytecode has no throws clause
  2. Java error: 'exception is never thrown in try block'
  3. @Throws(IOException::class) re-adds the throws clause
  4. Interop-only: no effect on Kotlin callers or runtime
  5. Accepts multiple KClass args; works on suspend too

basics

~20 s

Because Kotlin doesn't record a throws clause in the compiled method, Java thinks the exception can't happen and refuses to let you catch it. Adding @Throws puts the clause back so Java can catch it.

solid answer

~40 s

Kotlin has no checked exceptions, so by default a compiled Kotlin function has **no `throws` clause** in its bytecode signature — even if it actually throws `IOException`. When Java calls that function and writes `catch (IOException e)`, the Java compiler sees no declared checked exception in the try block and reports *"exception IOException is never thrown in the corresponding try block."* The fix is the `@Throws(IOException::class)` annotation on the Kotlin function: it adds the checked exception to the generated method's `throws` clause, so the Java compiler permits (and where required, demands) the catch/declare. `@Throws` accepts multiple `KClass` arguments. It is interop-only — it has no effect on Kotlin callers and does not introduce checked-exception enforcement within Kotlin.

code

kotlin · 7 lines
kotlin
import java.io.IOException
import java.sql.SQLException

@Throws(IOException::class, SQLException::class)
fun loadAndStore(path: String) {
    // body may throw either checked exception; Java callers can now catch them
}

go deeper

for a junior

Recognizes that @Throws lets Java catch a checked exception thrown by Kotlin.

for a middle

Explains that Kotlin omits the throws clause and that the Java compiler then rejects the catch.

for a senior

Details the compile-time signature mechanics, multiple-type usage, and that it's interop-only with no Kotlin-side effect.

for a principal

Designs cross-language API contracts: when to surface checked exceptions vs Result/sealed errors, and framework reflection dependencies on throws clauses.

## The root cause Java's compiler tracks **checked exceptions**: it forbids catching a checked type that the try block's methods don't declare via `throws`. Kotlin, having no checked exceptions, omits the `throws` clause from compiled method signatures by default. So a Kotlin function that genuinely throws `IOException` *looks* exception-free to Java. ```kotlin // Kotlin fun risky() { throw java.io.IOException("boom") } ``` ```java // Java caller — DOES NOT COMPILE try { KotlinFileKt.risky(); } catch (IOException e) { // error: IOException is never thrown in try block handle(e); } ``` ## The fix: @Throws Annotate the Kotlin declaration so the compiler emits the `throws` clause: ```kotlin @Throws(java.io.IOException::class) fun risky() { throw java.io.IOException("boom") } ``` Now the generated method is effectively `void risky() throws IOException`, and the Java `catch (IOException e)` compiles. You can list several types: `@Throws(IOException::class, SQLException::class)`. ## What @Throws does NOT do - It does **not** add checked-exception enforcement for **Kotlin** callers — they still need no try/catch. - It does **not** change runtime behavior — the exception was always thrown; this only affects the **compile-time signature** Java sees. - It is **not** required when Kotlin calls Kotlin, or even when Java calls a Kotlin function whose exception you don't intend to catch as a checked type. ## When you actually need it - Java callers must `catch`/`declare` a specific checked type from your Kotlin API. - Frameworks that **reflect** on the `throws` clause (some test runners, RMI, certain serialization/proxy libraries) expect it. - Coroutines: `@Throws` can also annotate `suspend` functions; the clause appears on the generated continuation-based signature for Java interop. ## Relationship to this leaf This is the mirror image of "calling Java that throws checked exceptions": that direction is frictionless (no try/catch forced); this direction needs `@Throws` to re-expose the contract Java relies on. ## Key keywords/APIs `@Throws(vararg exceptionClasses: KClass<out Throwable>)`, checked vs unchecked exceptions, the JVM method `throws` clause, `suspend` interop.

  • Does @Throws make Kotlin callers wrap the call in try/catch?
    No. Kotlin still has no checked exceptions; @Throws only affects how Java sees the method signature.
  • Can you use @Throws on a suspend function?
    Yes. It declares the checked exception on the generated Java-facing signature, which matters for Java interop with coroutine-based code.

@Throws is like adding an allergen label back onto a product so a strict inspector (the Java compiler) will let it through customs — the contents never changed.

saying these in an interview costs you the question

  • Saying @Throws enables checked-exception enforcement inside Kotlin
  • Thinking the runtime behavior changes (it doesn't — only the signature)
  • Believing Java can always catch any Kotlin-thrown exception by default
  • Claiming @Throws is needed when Kotlin calls Kotlin

context