- Kotlin
Kotlin Result型|runCatching・getOrElse・map一覧
Result<T> は、「成功した値 T」か「失敗した例外 Throwable」のどちらかを1つの値として持つ型です。 runCatching { } で例外を Result に変え、getOrElse・map・fold などで取り出したり変えたりします。標準ライブラリにあり、追加の依存はいりません。
fun parse(s: String): Result<Int> = runCatching { s.toInt() }
parse("42") // Success(42)
parse("abc") // Failure(java.lang.NumberFormatException: For input string: "abc")
parse("abc").getOrElse { -1 } // -1
parse("42").map { it * 2 } // Success(84)
使い方の目安です。
| 場面 | Result が向いているか |
|---|---|
| 例外を投げる処理を、例外を外に出さずにまとめて扱う | 向いている |
| 複数の処理の成功・失敗を、全部集めてから見る | 向いている |
| 「見つからない」「入力が不正」など、業務上の失敗を種類ごとに分ける | 向いていない(sealed class を使う) |
コルーチンの suspend 関数を包む |
注意が要る(キャンセルも捕まえる) |
確認環境:Kotlin 2.4.20(kotlinc 2.4.20、kotlin-stdlib 2.4.20)、kotlinx-coroutines-core 1.8.0、OpenJDK 21.0.10、Linux。この記事の早見表の値は、関数ごとに成功・失敗の両方を実行して出た出力です。関数の一覧と説明は kotlin-stdlib 2.4.20 のソース(Result.kt)と kotlinlang.org の API リファレンス、設計の意図は KEEP の「Result」提案で確かめました。Arrow の Either は arrow-kt.io のドキュメントで確かめた内容です(確認日 2026年10月13日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Kotlin Result型|runCatching・getOrElse・map一覧」
Result 型とは
kotlin-stdlib 2.4.20 のソースでは、Result は次のように定義されています。
@SinceKotlin("1.3")
@JvmInline
public value class Result<out T> @PublishedApi internal constructor(
@PublishedApi
internal val value: Any?
) : Serializable
- 値クラス(value class):成功のときは値そのものを、失敗のときは例外を包んだ
Failureを中に持ちます。成功の値を余分な箱に入れないための形です - コンストラクタは
internal:Result(…)とは書けず、Result.success()・Result.failure()・runCatchingで作ります - 失敗の型は
Throwableに固定:Result<User, NotFound>のように、失敗の型を決めることはできません
例外処理全体(try-catch と runCatching のどちらを使うか)は、関連記事「Kotlin例外処理 try-catchとrunCatchingの使い分け」で扱っています。この記事は Result 型そのものの関数と、設計の注意に絞ります。
関数の早見表(成功・失敗の両方で実行)
ok = parse("42")(Success(42))と ng = parse("abc")(Failure(NumberFormatException))に対して、各関数を実行した結果です。
作る
| 書き方 | 実行結果 |
|---|---|
runCatching { s.toInt() } |
Success(42) / Failure(java.lang.NumberFormatException: For input string: "abc") |
"12".runCatching { toInt() * 2 } |
Success(24)(受け取り側を this にする版) |
Result.success(1) |
Success(1) |
Result.failure<Int>(IOException("x")) |
Failure(java.io.IOException: x) |
調べる・取り出す
| 関数 | 成功(ok) | 失敗(ng) |
|---|---|---|
isSuccess / isFailure |
true / false |
false / true |
getOrNull() |
42 |
null |
exceptionOrNull() |
null |
NumberFormatException |
getOrDefault(0) |
42 |
0 |
getOrElse { e -> -1 } |
42 |
-1(e は NumberFormatException) |
getOrThrow() |
42 |
NumberFormatException を投げる |
fold({ "値=$it" }, { "失敗=${it.message}" }) |
値=42 |
失敗=For input string: "abc" |
変える
| 関数 | 成功(ok) | 失敗(ng) |
|---|---|---|
map { it * 2 } |
Success(84) |
元の Failure のまま |
mapCatching { … } |
中の例外を Failure にする |
元の Failure のまま |
recover { 0 } |
Success(42) のまま |
Success(0) |
recoverCatching { … } |
Success(42) のまま |
中の例外を Failure にする |
副作用(ログなど)
| 関数 | 動き |
|---|---|
onSuccess { … } |
成功のときだけ動き、同じ Result を返す |
onFailure { … } |
失敗のときだけ動き、同じ Result を返す |
ok.onSuccess { println(" onSuccess: $it") }.onFailure { println(" onFailure: $it") }
ng.onSuccess { println(" onSuccess: $it") }.onFailure { println(" onFailure: ${it::class.simpleName}") }
onSuccess: 42
onFailure: NumberFormatException
kotlin-stdlib 2.4.20 の Result.kt にある公開の関数・プロパティは、この表の18個(runCatching 2種・success・failure・isSuccess・isFailure(この2つはプロパティ)・getOrNull・exceptionOrNull・getOrDefault・getOrElse・getOrThrow・fold・map・mapCatching・recover・recoverCatching・onSuccess・onFailure)と toString です。flatMap・zip・filter はありません。
map と mapCatching の違い:中で例外が起きたとき
名前の似た関数の違いは、渡したラムダの中で例外が起きたときに投げるか、Failure にするかです。
println("map の中で例外: " + try { ok.map { check(it < 10) { "大きすぎ" }; it } } catch (e: Exception) { "map が投げた: $e" })
println("mapCatching の中で例外: ${ok.mapCatching { check(it < 10) { "大きすぎ" }; it }}")
println("recover の中で例外: " + try { ng.recover { error("recover 失敗") } } catch (e: Exception) { "recover が投げた: $e" })
println("recoverCatching の中で例外: ${ng.recoverCatching { error("recover 失敗") }}")
map の中で例外: map が投げた: java.lang.IllegalStateException: 大きすぎ
mapCatching の中で例外: Failure(java.lang.IllegalStateException: 大きすぎ)
recover の中で例外: recover が投げた: java.lang.IllegalStateException: recover 失敗
recoverCatching の中で例外: Failure(java.lang.IllegalStateException: recover 失敗)
| 関数 | ラムダの中の例外 |
|---|---|
map・recover・fold・getOrElse・onSuccess・onFailure |
そのまま投げる |
mapCatching・recoverCatching |
Failure に入れる |
map の中で例外が起きると、Result を使っていても例外が外へ出ました。 ラムダの中が失敗しうる処理なら mapCatching を使います。
flatMap が無いときの書き方
Result を返す関数を map でつなぐと、Result が入れ子になります。
fun half(n: Int): Result<Int> =
if (n % 2 == 0) Result.success(n / 2) else Result.failure(IllegalArgumentException("奇数: $n"))
val nested: Result<Result<Int>> = parse("42").map { half(it) }
println("map で入れ子: $nested")
println("mapCatching + getOrThrow: ${parse("42").mapCatching { half(it).getOrThrow() }}")
println("mapCatching + getOrThrow(奇数): ${parse("7").mapCatching { half(it).getOrThrow() }}")
fun <T, R> Result<T>.flatMap(f: (T) -> Result<R>): Result<R> = fold({ f(it) }, { Result.failure(it) })
println("自作 flatMap: ${parse("42").flatMap(::half)} / ${parse("7").flatMap(::half)} / ${parse("x").flatMap(::half)}")
map で入れ子: Success(Success(21))
mapCatching + getOrThrow: Success(21)
mapCatching + getOrThrow(奇数): Failure(java.lang.IllegalArgumentException: 奇数: 7)
自作 flatMap: Success(21) / Failure(java.lang.IllegalArgumentException: 奇数: 7) / Failure(java.lang.NumberFormatException: For input string: "x")
KEEP の「Result」提案では、flatMap を入れなかった理由として、Kotlin ではふつうの手続きの書き方でつなげる、と説明されています。いちばん素直なのは、runCatching の中で getOrThrow() を並べる形です。
fun quarter(s: String): Result<Int> = runCatching {
val n = parse(s).getOrThrow()
val h = half(n).getOrThrow()
half(h).getOrThrow()
}
8 -> Success(2), 6 -> Failure(java.lang.IllegalArgumentException: 奇数: 3), x -> Failure(java.lang.NumberFormatException: For input string: "x")
途中で失敗すると getOrThrow() が例外を投げ、外側の runCatching が Failure にしました。"6" は1回目の half で 3 になり、2回目の half で失敗しています。
リストの Result をまとめる
listOf("1", "2", "x", "4").map(::parse).let { rs ->
"成功=${rs.mapNotNull { it.getOrNull() }} 失敗数=${rs.count { it.isFailure }}" }
リスト: 成功=[1, 2, 4] 失敗数=1
1件の失敗で全体を止めず、成功と失敗を分けて扱えます。KEEP でも、複数の処理の結果を全部集める使い方が、Result の用途の1つに挙げられています。
例外を Result にする場面・しない場面
KEEP の「Result」提案には、「Result クラスは、業務の(ドメイン固有の)エラーを表すためには作られていない」と書かれています。挙げられている用途は次の4つです。
| KEEP が挙げる用途 | 例 |
|---|---|
コルーチンの継続(Continuation.resumeWith) |
標準ライブラリ内部 |
| 並列に分けた処理の結果を集める | 全部の結果を見てから判断 |
| 複数の失敗をまとめて処理する | 複数ファイルの読み込みで、失敗したものを記録 |
try-catch の代わりに関数でつなぐ |
onFailure { log(it) } |
業務上の失敗(「ユーザーが見つからない」「名前の形式が違う」)には、null を返すか sealed class を使う、と KEEP は勧めています。
Error も捕まえる
runCatching は Throwable を捕まえるので、Exception でない Error も Failure になります。
fun deep(n: Int): Int = deep(n + 1) + 1
println("runCatching { 無限の再帰 }: " + runCatching { deep(0) }.exceptionOrNull()?.let { it::class.qualifiedName })
runCatching { 無限の再帰 }: java.lang.StackOverflowError
StackOverflowError や OutOfMemoryError は、プログラムの誤りや資源不足を表します。getOrElse { 既定値 } で黙って既定値にすると、原因に気づきにくくなります。onFailure でログに出すか、exceptionOrNull() が Exception かどうかを見て扱いを分けます。
成功の null と失敗の null
val r: Result<String?> = runCatching { null }
println("runCatching { null }: $r getOrNull=${r.getOrNull()} isSuccess=${r.isSuccess}")
runCatching { null }: Success(null) getOrNull=null isSuccess=true
値が null になりうる型では、getOrNull() の null が「成功して null」か「失敗」か区別できません。isSuccess か fold で見分けます。
CancellationException の注意
コルーチンのキャンセルは CancellationException を投げるしくみです。runCatching はこれも Failure にするので、キャンセルされたのに処理が続きます。
suspend fun fetch(): String { delay(100); return "data" }
runBlocking {
val job = launch {
repeat(3) { n -> println(" $n: ${runCatching { fetch() }}") }
println(" ループを最後まで回った")
}
delay(150); job.cancelAndJoin()
}
0: Success(data)
1: Failure(kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@6a5fc7f7)
2: Failure(kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@6a5fc7f7)
ループを最後まで回った
suspend 関数を包むときは、CancellationException だけ投げ直す関数を用意します。
inline fun <T> runCatchingCancellable(block: () -> T): Result<T> =
try { Result.success(block()) }
catch (e: kotlinx.coroutines.CancellationException) { throw e }
catch (e: Throwable) { Result.failure(e) }
この関数で同じループを動かすと、キャンセルの後でループが止まりました。コルーチンの例外の伝わり方と、この関数を使った実行結果は、関連記事「Kotlinコルーチン例外処理|SupervisorJobとHandlerの使い分け」で扱っています。
関数の戻り値に使うときの注意
KEEP によると、Kotlin 1.3〜1.4 では Result を関数の戻り値にできませんでした。この制限は Kotlin 1.5 で外れ、今は fun parse(s: String): Result<Int> と書けます。ただし、値クラスであることから来る注意があります。
JVM では Object 型になり、メンバー関数は名前が変わる
class ApiClient { fun get(): Result<String> = Result.success("ok") }
class Repo {
fun find(id: Int): Result<String> = Result.success("u$id")
fun take(r: Result<String>): String = r.getOrElse { "x" }
val last: Result<Int> = Result.success(1)
}
fun parse(s: String): Result<Int> = runCatching { s.toInt() } // トップレベル
コンパイルしたクラスを javap で見た結果です。
public final class ApiClient {
public final java.lang.Object get-d1pmJ48();
public final class MainKt {
public static final java.lang.Object parse(java.lang.String);
public final class Repo {
public final java.lang.Object find-IoAF18A(int);
public final java.lang.String take(java.lang.Object);
public final java.lang.Object getLast-d1pmJ48();
}
- 戻り値の型は、どれも
java.lang.Objectでした - クラスのメンバー関数とプロパティは、名前の後ろに
-d1pmJ48のような文字が付きました(名前の変形、mangling)。Java のコードからは、この名前ではふつうに呼べません - トップレベル関数の
parseは名前が変わりませんでした
Java から parse を呼んで、戻り値のクラスを出した結果です。
Object ok = MainKt.parse("42"); Object ng = MainKt.parse("abc");
System.out.println(ok + " : " + ok.getClass().getName());
System.out.println(ng + " : " + ng.getClass().getName());
42 : java.lang.Integer
Failure(java.lang.NumberFormatException: For input string: "abc") : kotlin.Result$Failure
成功は値そのもの(Integer)、失敗は kotlin.Result$Failure が返りました。 Java 側から見ると型の情報が無く、扱いにくい形です。
公開 API での目安
| 場面 | 目安 |
|---|---|
| モジュールの中だけで使う | 戻り値に Result を使っても扱いやすい |
| Java からも呼ばれるライブラリ | 名前の変形と Object 型があるので、例外を投げる関数か自作の型を公開する |
| 失敗の種類を呼び出し側に伝えたい | Throwable しか持てないので、sealed class を使う |
| Spring などのフレームワークに戻り値を渡す | 値クラスの扱いはフレームワークの対応次第。中で getOrThrow() してから返す形が単純 |
値クラスのしくみは、関連記事「Kotlin value classの使い方|IDの取り違えを型で防ぐ」で扱っています。
等しさ
Result.success(1) == Result.success(1): true
同じ例外の failure 同士: true
別の例外(同じ文): false
Failure の比較は、中の例外の equals で決まります。例外は同じメッセージでも別のオブジェクトなら等しくないので、テストでは exceptionOrNull() の型やメッセージを比べます。
sealed class で自作する方法との比較
失敗の種類を型で表したいときは、sealed(封印された)クラスやインターフェースで自作します。
sealed interface LoadError {
data object NotFound : LoadError
data class Invalid(val reason: String) : LoadError
}
sealed interface Outcome<out T> {
data class Ok<T>(val value: T) : Outcome<T>
data class Err(val error: LoadError) : Outcome<Nothing>
}
fun load(id: Int): Outcome<String> = when {
id <= 0 -> Outcome.Err(LoadError.Invalid("id は1以上"))
id > 100 -> Outcome.Err(LoadError.NotFound)
else -> Outcome.Ok("user$id")
}
fun message(o: Outcome<String>) = when (o) {
is Outcome.Ok -> "成功: ${o.value}"
is Outcome.Err -> when (val e = o.error) {
LoadError.NotFound -> "見つかりません"
is LoadError.Invalid -> "入力エラー: ${e.reason}"
}
}
load(0) -> Err(error=Invalid(reason=id は1以上)) -> 入力エラー: id は1以上
load(5) -> Ok(value=user5) -> 成功: user5
load(200) -> Err(error=NotFound) -> 見つかりません
when に else を書いていないので、LoadError に種類を足すと、message の when がコンパイルエラーになり、処理の書き忘れに気づけます。
| 比べる点 | 標準の Result | sealed で自作 | Arrow の Either |
|---|---|---|---|
| 失敗の型 | Throwable 固定 |
自由に決める | Either<E, A> の E で自由 |
| 依存 | 不要 | 不要 | arrow-core |
| 例外からの変換 | runCatching |
自分で try-catch |
Either.catch { } |
| つなぐ書き方 | getOrThrow() を runCatching で包む |
when で分ける |
either { } の中で bind() |
when での網羅チェック |
無し | あり | Left・Right で分けられる |
sealed class の書き方は、関連記事「Kotlin sealed classの使い方 enumとの違いとwhen網羅」で扱っています。
Arrow の Either(ドキュメントの紹介)
Arrow は Kotlin の関数型プログラミング向けライブラリです。GitHub のタグで見た最新版は 2.2.3 です。arrow-kt.io の「Either & Ior」のページでは、Either<E, A> は失敗 E(Left)か成功 A(Right)を持つ型とされ、次のように書かれています。
import arrow.core.Either
import arrow.core.left
import arrow.core.right
import arrow.core.raise.either
import arrow.core.raise.ensure
data class MyError(val message: String)
fun isPositive(i: Int): Either<MyError, Int> = either {
ensure(i > 0) { MyError("$i is not positive") }
i
}
ドキュメントの説明をまとめると次のとおりです。
| 書き方 | 意味 |
|---|---|
x.left() / x.right() |
失敗・成功の値を作る |
either { … } |
中で bind()・ensure() が使えるブロック。最初の失敗で止まる |
e.bind() |
Right なら値を取り出し、Left ならブロックをその失敗で終える |
ensure(条件) { 失敗 } |
条件が偽なら失敗で終える |
Either.catch { } |
例外を Left にする(runCatching の Either 版) |
zipOrAccumulate・mapOrAccumulate |
最初で止めず、失敗を全部集める |
標準の Result と比べると、失敗の型を決められる点と、either { } でつなぎを書ける点が違います。チームで関数型の書き方をそろえたい場合の候補になります。
よくある質問
Q. Result を関数の戻り値にしてよいですか。
A. Kotlin 1.5 以降は書けます。モジュールの中では扱いやすい一方、JVM では戻り値が Object 型になり、クラスのメンバー関数は名前が変わりました(上の javap の結果)。Java から呼ばれる API や、失敗の種類を伝えたい API では、sealed class か例外を使う形が扱いやすくなります。
Q. getOrElse と getOrDefault の違いは何ですか。
A. getOrDefault(値) は失敗のとき決まった値を返します。getOrElse { e -> … } は失敗の例外を受け取って、値を計算して返します。例外の種類で返す値を変えたいときや、既定値の計算が重いときは getOrElse です。
Q. Result を List に入れたり、プロパティに持ったりできますか。
A. できます。上の例でも List<Result<Int>> を作り、val last: Result<Int> をプロパティにしました。ただし、プロパティの getter も JVM では名前が変わりました(getLast-d1pmJ48)。
Q. Result の失敗に、自分で作った例外を入れてもよいですか。
A. Result.failure(MyException(…)) で入れられます。exceptionOrNull() で取り出して is で型を見れば種類で分けられますが、when の網羅チェックは効きません。種類で分けたいことが多いなら sealed class の方が向いています。
まとめ
Result<T>は成功の値かThrowableを持つ値クラス。runCatching・success・failureで作る- 取り出しは
getOrNull・getOrDefault・getOrElse・getOrThrow・fold mapの中の例外は外へ出た。mapCatchingならFailureになったflatMapは無い。runCatching { a().getOrThrow(); b().getOrThrow() }の形でつなぐrunCatchingはErrorもCancellationExceptionも捕まえた。suspend関数には投げ直す版を使う- JVM では戻り値が
Object、メンバー関数は名前が変わった。Java 向けの公開 API には向かない - 業務の失敗の種類を分けるなら sealed class、関数型の書き方でそろえるなら Arrow の
Either
参考・出典
確認日はいずれも 2026年10月13日です。
- Kotlin API「Result」:https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-result/
- Kotlin API「runCatching」:https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/run-catching.html
- KEEP「Result」:https://github.com/Kotlin/KEEP/blob/master/proposals/stdlib/result.md
- Kotlin「Inline value classes」:https://kotlinlang.org/docs/inline-classes.html
- Arrow「Either & Ior」:https://arrow-kt.io/learn/typed-errors/either-and-ior/
- Android Developers「Best practices for coroutines in Android」:https://developer.android.com/kotlin/coroutines/coroutines-best-practices
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Kotlin Result型|runCatching・getOrElse・map一覧」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。