Careeroza
The Result Type
runCatching and Result
Result<T> holds either a success value or an exception. runCatching { } runs a block and captures whatever it returns or throws. Errors become ordinary values you can pass around and handle later.
Example
fun main() {
val ok: Result<Int> = runCatching { "42".toInt() }
val bad: Result<Int> = runCatching { "4x2".toInt() }
println(ok.isSuccess)
println(ok.getOrNull())
println(bad.isFailure)
println(bad.exceptionOrNull()?.message)
println(bad.getOrDefault(0))
println(bad.getOrElse { e -> -1 })
}Output
true 42 true For input string: "4x2" 0 -1
Transforming and Handling Results
Example
fun parseAmount(text: String): Result<Double> = runCatching {
val value = text.toDouble()
require(value > 0) { "Amount must be positive" }
value
}
fun main() {
for (input in listOf("250.5", "-10", "abc")) {
parseAmount(input)
.map { it * 1.18 } // only on success
.onSuccess { println("$input -> with GST %.2f".format(it)) }
.onFailure { println("$input -> failed: ${it.message}") }
}
val message = parseAmount("99").fold(
onSuccess = { "Pay Rs $it" },
onFailure = { "Invalid amount" },
)
println(message)
println(parseAmount("x").recover { 0.0 }.getOrThrow())
}Output
250.5 -> with GST 295.59 -10 -> failed: Amount must be positive abc -> failed: For input string: "abc" Pay Rs 99.0 0.0
Exceptions vs Result
| Use | When |
|---|---|
Exceptions (throw) | programming errors and truly unexpected failures; let them crash early in development |
Result / runCatching | expected failures the caller should handle: network, parsing, validation |
Nullable return (toIntOrNull) | simple "no value" cases where the reason does not matter |
| Sealed result class | when callers need typed, domain-specific errors (e.g. NoInternet, Unauthorized) |
Note: runCatching catches every Throwable, including coroutine CancellationException. In coroutine code rethrow cancellation (covered in the advanced level).