Several Kotlin stdlib functions (like readText or use) already carry @Throws. Why, and what is the common pitfall when a Kotlin function delegates to Java code that throws a checked exception without re-declaring it?
answer
- Stdlib I/O wrappers carry @Throws(IOException::class)
- Kotlin wraps Java checked exceptions silently
- No @Throws = Java can't catch the propagated exception
- @Throws is manual and unverified by the compiler
- Keep it in sync with the Java callee's throws
basics
~20 sStdlib functions that wrap Java I/O carry @Throws so Java callers can handle the IOException Java would normally force them to. The pitfall: your own Kotlin wrapper around Java I/O won't declare anything unless you add @Throws yourself, so Java callers can't catch it.
solid answer
~40 sMany Kotlin stdlib I/O helpers (e.g. `Reader.readText`, `Closeable.use`, file extensions) are annotated with `@Throws(IOException::class)` precisely so that **Java** consumers of the Kotlin stdlib retain the checked-exception ergonomics they expect from Java I/O. The common pitfall is in your own code: when a Kotlin function calls Java code that throws a **checked** exception, Kotlin lets it propagate freely with no compile-time fuss, and the generated method gets **no `throws` clause**. A Java caller of your Kotlin wrapper then cannot `catch` that checked exception (or, if they try, gets *"exception never thrown"*). The fix is to add `@Throws` listing the exception(s) the Kotlin function can propagate. So `@Throws` does not detect anything for you — you must manually keep it in sync with what the Java code underneath actually throws.
go deeper
Understands that Java I/O throws IOException and that Kotlin wrappers need @Throws for Java to catch it.
Explains the silent propagation pitfall and that @Throws is manual, not inferred.
Discusses keeping @Throws in sync as the callee evolves and confining it to a Java-facing layer.
Designs the interop boundary policy and uses tests/conventions to prevent drift between actual and declared exceptions.
## Why stdlib uses `@Throws` Kotlin's standard library wraps a lot of Java I/O. Java's I/O methods throw **checked** `IOException`, and Java developers expect to handle it. If the Kotlin stdlib wrappers emitted no `throws` clause, Java users of those wrappers would lose the ability to catch `IOException` cleanly. So the stdlib annotates them, e.g. (conceptually): ```kotlin @Throws(IOException::class) fun Reader.readText(): String { /* ... */ } ``` This keeps the Kotlin stdlib a **good Java citizen** at the I/O boundary. ## The propagation pitfall In Kotlin, calling Java code that throws a checked exception requires **no handling** — Kotlin treats it as unchecked. Consider: ```kotlin fun copyConfig(src: String, dst: String) { // java.nio.file.Files.copy throws checked IOException java.nio.file.Files.copy( java.nio.file.Path.of(src), java.nio.file.Path.of(dst) ) } ``` This compiles in Kotlin with no `try/catch`. But the generated `copyConfig` has **no `throws` clause**. A Java caller: ```java try { Util.copyConfig("a", "b"); } catch (IOException e) { ... } // compile error: never thrown ``` fails to compile, even though `IOException` absolutely can occur at runtime. The exception **does** still propagate at runtime; it is only the **static declaration** that is missing. ## The fix Add `@Throws` to mirror what the underlying Java code throws: ```kotlin import java.io.IOException @Throws(IOException::class) fun copyConfig(src: String, dst: String) { java.nio.file.Files.copy( java.nio.file.Path.of(src), java.nio.file.Path.of(dst) ) } ``` ## The maintenance trap `@Throws` is **manual and unverified**. The Kotlin compiler does not check that the listed exceptions are actually possible, nor does it warn if you omit one that the Java callee declares. So: - If the underlying Java code starts throwing a **new** checked exception, your `@Throws` will silently be incomplete and Java callers cannot catch the new type. - If you over-declare an exception that can never occur, Java callers may be forced into dead `catch` blocks. This is why `@Throws` is best confined to a **deliberate Java-facing API layer**, kept in sync by humans (and ideally tests), rather than sprinkled everywhere. ## Takeaways - Stdlib annotates I/O wrappers for Java ergonomics. - Your Kotlin wrappers over Java I/O are silent to Java unless you add `@Throws`. - The annotation is manual; keep it accurate as the callee evolves.
- Does the compiler verify the exceptions you list in @Throws are actually possible?No. @Throws is unchecked metadata; nothing validates that the listed types can occur or that you haven't missed one the callee throws.
- At runtime, is the checked exception lost if you forget @Throws?No — it still propagates at runtime exactly the same. Only the static throws declaration (for Java's compiler) is missing.
saying these in an interview costs you the question
- Thinking the compiler infers/verifies @Throws automatically
- Believing a missing @Throws suppresses the exception at runtime
- Not knowing stdlib I/O wrappers are annotated for Java
- Sprinkling @Throws everywhere instead of a deliberate API layer