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期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?