throw raises an exception; try/catch/finally handles it, functioning almost identically to Java's syntax on the surface. The genuinely notable difference is that Kotlin has no checked exceptions whatsoever — no function is ever required to declare throws, and no caller is ever forced to catch or re-declare an exception, a deliberate rejection of Java's design that JetBrains has explained as leading to more boilerplate and swallowed exceptions in practice than genuine safety. Like if and when, try can be used as an expression, directly producing a value from whichever branch actually ran.
Basic try/catch/finally
finally always runs, whether or not an exception occurred — the standard place for cleanup that must happen regardless of success or failure.
fun divide(a: Int, b: Int): Int {
return try {
a / b
} catch (e: ArithmeticException) {
println("Error: ${e.message}")
0
} finally {
println("Division attempted")
}
}
println(divide(10, 0)) // "Division attempted" then 0No checked exceptions: a deliberate design choice
readFile compiles fine with zero throws declaration and zero forced try/catch at the call site — Kotlin trusts the programmer to handle exceptions where it genuinely makes sense, rather than mechanically enforcing it everywhere.
import java.io.File
fun readFile(path: String): String {
return File(path).readText() // this CAN throw IOException — but no 'throws' needed anywhere
}
val content = readFile("data.txt") // no forced try/catch here either — entirely the caller's choicetry as an expression
Because try produces a value directly, there's no need for a separate mutable variable declared before the try block and reassigned inside it — the result flows out naturally, the same pattern if and when use.
val number: Int = try {
"42".toInt()
} catch (e: NumberFormatException) {
-1 // fallback value if parsing fails
}
println(number) // 42