try is an expression: the value of the try block, or of whichever typed catch ran, is the result, and finally runs without changing it. require() throws IllegalArgumentException; check() and error() throw IllegalStateException.
fun parseQuantity(input: String): Int {
val n = input.trim().toInt() // may throw NumberFormatException
require(n in 1..10) { "Quantity must be 1-10, was $n" }
return n
}
fun main() {
for (input in listOf(" 7 ", "two", "40")) {
val qty = try {
parseQuantity(input)
} catch (e: NumberFormatException) { 1 } // the subclass must come first
catch (e: IllegalArgumentException) { println(e.message); 10 }
println("'$input' -> $qty")
}
}' 7 ' -> 7 'two' -> 1 Quantity must be 1-10, was 40 '40' -> 10
NumberFormatException extends IllegalArgumentException, and swapped clauses compile without a warning, sending "two" to the general handler. Kotlin has no checked exceptions, so nothing forces you to declare or catch anything (@Throws documents them for Java callers). throw has the type Nothing, so catalog.find { ... } ?: error("not found") is still a Book. For expected failures, prefer toIntOrNull() with ?:, or runCatching { }, which returns a Result. Cancellation covers coroutines.