# How to Handle Kotlin Exceptions When a Method Throws: A Complete Guide to Result and runCatching

> Master Kotlin exceptions with runCatching. Learn to gracefully handle errors using Result and avoid nested try catch blocks for cleaner code.

- Repository: [JetBrains/kotlin](https://github.com/jetbrains/kotlin)
- Tags: how-to-guide
- Published: 2026-02-20

---

**Use the `runCatching` helper to wrap exception-throwing code in a `Result` type, then chain methods like `getOrElse`, `getOrThrow`, or `fold` to handle success and failure cases without nested try/catch blocks.**

Kotlin provides a type-safe, functional approach to handle kotlin exceptions through the standard library's `Result` type and `runCatching` utility. Unlike Java's checked exceptions or basic try/catch blocks, these tools allow you to capture any `Throwable` in a discriminated union that preserves both success values and failure states. According to the JetBrains/kotlin source code, the core implementation resides in [`libraries/stdlib/src/kotlin/util/Result.kt`](https://github.com/JetBrains/kotlin/blob/main/libraries/stdlib/src/kotlin/util/Result.kt), where the `runCatching` inline function (lines 41-61) automatically wraps execution outcomes while catching all throwables.

## Understanding the Result Type and runCatching

The `Result<T>` class is a value class defined in [`libraries/stdlib/src/kotlin/util/Result.kt`](https://github.com/JetBrains/kotlin/blob/main/libraries/stdlib/src/kotlin/util/Result.kt) that represents either a successful value of type `T` or a failure containing a `Throwable`. This discriminated union eliminates the need for null checks or separate error flags.

The `runCatching` function provides the primary entry point to handle kotlin exceptions functionally:

```kotlin
@InlineOnly
@SinceKotlin("1.3")
public inline fun <R> runCatching(block: () -> R): Result<R> {
    return try {
        Result.success(block())
    } catch (e: Throwable) {
        Result.failure(e)
    }
}

```

This inline function catches **any** `Throwable`, including unchecked exceptions and errors, wrapping them in a `Result.failure` instance. Because it is inline, the lambda body compiles directly into the call site with zero overhead from function objects.

## Core Methods for Extracting Values and Handling Failures

Once you have a `Result` instance, the standard library provides several extension functions to handle kotlin exceptions without explicit try/catch blocks. These are implemented in [`libraries/stdlib/src/kotlin/util/Result.kt`](https://github.com/JetBrains/kotlin/blob/main/libraries/stdlib/src/kotlin/util/Result.kt) (lines 71-84).

### getOrElse and getOrThrow

Use `getOrElse` to provide a fallback value when an exception occurs:

```kotlin
val content = runCatching { File("config.json").readText() }
    .getOrElse { throwable ->
        logger.warn("Could not read config: ${throwable.message}")
        "{}"  // Return default empty JSON
    }

```

Use `getOrThrow` when you want to re-throw the captured exception:

```kotlin
val criticalValue = runCatching { fetchMandatoryData() }
    .getOrThrow()  // Throws the original Throwable if failure

```

### fold for Explicit Branching

The `fold` method combines success and failure handling into a single expression:

```kotlin
runCatching { processPayment() }.fold(
    onSuccess = { transactionId -> println("Success: $transactionId") },
    onFailure = { error -> println("Failed: ${error.message}") }
)

```

### getOrNull and exceptionOrNull

For cases where you only care about the value and want to ignore errors:

```kotlin
val maybeValue: Int? = runCatching { parseInt(input) }.getOrNull()
val error: Throwable? = runCatching { riskyOperation() }.exceptionOrNull()

```

## Chaining Operations with map and flatMap

Functional composition allows you to handle kotlin exceptions across multiple dependent operations without nested try/catch blocks.

### Transforming Success Values with map

```kotlin
val finalResult = runCatching { fetchUserId() }
    .map { id -> fetchUserProfile(id) }  // Only runs if fetchUserId succeeded
    .map { profile -> profile.email }

```

### Chaining Multiple Result-Producing Operations with flatMap

When subsequent operations might also throw, use `flatMap` to avoid nested `Result` types:

```kotlin
val composedResult = runCatching { step1() }
    .flatMap { intermediate ->
        runCatching { step2(intermediate) }  // Returns Result, flattened automatically
    }
    .flatMap { runCatching { step3(it) } }

```

## Comparing runCatching with Classic Try/Catch

While `runCatching` provides elegant functional handling, classic `try / catch` blocks remain appropriate in specific scenarios.

| Approach | Best For | Syntax Example |
|----------|----------|----------------|
| **runCatching** | Functional pipelines, returning results from functions, composing operations | `runCatching { foo() }.map { bar(it) }.getOrElse { default }` |
| **try/catch** | Resource management (try-with-resources), performance-critical code, specific exception types | `try { file.use { it.readText() } } catch (e: IOException) { ... }` |

According to the implementation in [`libraries/stdlib/src/kotlin/util/Result.kt`](https://github.com/JetBrains/kotlin/blob/main/libraries/stdlib/src/kotlin/util/Result.kt), `runCatching` catches **any** `Throwable`, which includes `Error` subclasses like `OutOfMemoryError`. This differs from Java's checked exception model and provides uniform handling regardless of exception type.

## Practical Examples for Common Scenarios

### Handling File I/O Exceptions

```kotlin
import java.io.File

fun readConfig(path: String): Result<String> = runCatching {
    File(path).readText()  // May throw IOException
}

// Caller decides how to react
val content = readConfig("app.conf")
    .getOrElse { e ->
        logger.warn("Could not read config: ${e.message}")
        "{}"  // default empty JSON
    }
    .let { parseJson(it) }

```

### Network Request Error Handling

```kotlin
import java.net.URL

fun downloadData(url: String): Result<ByteArray> = runCatching {
    URL(url).readBytes()  // May throw IOException
}

val data = downloadData("https://api.example.com/data")
    .getOrElse { 
        logger.error("Download failed", it)
        ByteArray(0)  // empty payload as fallback
    }

```

### Composing Multiple Risky Operations

```kotlin
fun calculateRisky(): Double = runCatching { fetchNumber() }
    .map { it * 2 }
    .flatMap { doubled ->
        runCatching { divideByInput(doubled) }
    }
    .getOrElse { 0.0 }

```

## Summary

- **Use `runCatching`** to wrap exception-throwing code in a `Result` type without explicit try/catch blocks, as implemented in [`libraries/stdlib/src/kotlin/util/Result.kt`](https://github.com/JetBrains/kotlin/blob/main/libraries/stdlib/src/kotlin/util/Result.kt).
- **Leverage `getOrElse`, `getOrThrow`, and `fold`** to extract values, provide fallbacks, or branch on success/failure with functional syntax.
- **Chain operations** using `map` and `flatMap` to compose multiple exception-prone steps into clean, readable pipelines.
- **Reserve classic `try/catch`** for resource management (try-with-resources) or when you need to catch specific exception types without the overhead of `Result` allocation.
- **Remember** that `runCatching` catches any `Throwable`, providing uniform handling for checked, unchecked, and error conditions alike.

## Frequently Asked Questions

### What is the difference between runCatching and a try/catch block?

`runCatching` is an inline function that automatically wraps the outcome of a code block in a `Result` type, catching any `Throwable` and storing it as a failure. This allows functional chaining with `map`, `flatMap`, and `fold`. A classic `try/catch` block requires manual exception handling and does not return a composable result object, making `runCatching` preferable for functional pipelines while `try/catch` remains better for resource management with finally blocks.

### How do I extract a value from a Result or provide a default if it failed?

Use the `getOrElse` extension function, which accepts a lambda that receives the `Throwable` and returns a fallback value. For example: `runCatching { riskyCall() }.getOrElse { e -> defaultValue }`. If you prefer to re-throw the exception instead, use `getOrThrow()`, which returns the success value or throws the stored `Throwable` if the result represents a failure.

### Can I chain multiple operations that might throw exceptions?

Yes, use `map` to transform success values and `flatMap` to chain operations that themselves return `Result` types. For instance: `runCatching { step1() }.map { step2(it) }.flatMap { runCatching { step3(it) } }`. This creates a composable pipeline where any failure short-circuits subsequent operations, and you handle the final outcome with `fold`, `getOrElse`, or `getOrThrow` at the end of the chain.

### When should I avoid runCatching and use traditional try/catch instead?

Avoid `runCatching` in performance-critical sections where allocating a `Result` object introduces unacceptable overhead, or when you need fine-grained control over specific exception types without catching all `Throwable` subclasses. Additionally, use traditional `try/finally` or `use` blocks when managing resources that require guaranteed cleanup (like file streams or database connections), as `runCatching` does not inherently support the equivalent of a finally block for resource management.