
Type modeling operation outcomes as Success or Failure with status taxonomy, monadic composition (map/flatMap/fold), status-aware builders and optional action context for richer error handling.
A Kotlin Result<T, E> that also tells you the kind of success and failure.
Every Success and Failure carries a status classifying the kind of outcome, unlike Rust's Result, Swift's Result, or other Kotlin Result types, where success is just a bare value. A Success might be a plain success, a pending operation, or an intentional skip; a Failure might be unauthorized, invalid input, or a conflict. That status comes from kiit-codes, an already-established taxonomy reused here instead of inventing a new one. Outcome<T> is the ready-made alias for everyday use, paired with Try<T>, Option<T>, and Validated<T> for exceptions, absence, and validation.
| # | Topic | Description | |
|---|---|---|---|
| 1 | 💡 | Why | Problems kiit-result is designed to solve |
| 2 | 🚀 | Start | Installation and a quick example |
| 3 | 🧠 | Concepts |
Result/Success/Failure, Action, and the type aliases |
| 4 | 🏗️ | Builders | Status-aware factory methods for building a Result
|
| 5 | 🔁 | Conversions | Converting between Outcome, Try, and exceptions |
| 6 | ⚙️ | Usage | Use cases, and when (not) to reach for this |
| 7 | 🗺️ | Roadmap | Planned improvements and future work |
| 8 | 📖 | Learn More | Deeper documentation, design rationale, and FAQ on kiit.dev |
| 9 | 📋 | Requirements | Platforms and dependencies |
| 10 | 🤝 | Contributing | Build, test, and contribute |
| 11 | 📄 | License | Apache 2.0 license |
Most approaches capture whether something succeeded or failed, but do not have a mechanism for also indicating what the kind of success or failure it was. This is the primary gap that this library is aiming to solve.
kiit-result is a Result<T, E> with kiit-codes' closed status taxonomy built in. Success carries a Passed status, Failure carries a Failed status, and builders (restricted(), invalid(), ...) map each case to its matching category automatically. See Concepts and Builders for the full picture.
import kiit.codes.Err
import kiit.codes.Invalid
import kiit.codes.Rejected
import kiit.codes.Restricted
import kiit.codes.Succeeded
import kiit.result.Outcome
import kiit.result.Outcomes
class UserService {
private val users = mutableMapOf<String, User>()
// Alias Outcome<User> = Result<User, Err>
// Err is an error type from kiit-codes.
fun create(id: String, email: String): Outcome<User> = when {
// Restricted: a reserved id, not allowed
id == "admin" -> Outcomes.restricted(Restricted.DENIED)
// Invalid: bad input
email.isBlank() -> Outcomes.invalid(Err.on("email", email, "email is required"))
// Rejected: already exists
users.containsKey(id) -> Outcomes.rejected(Rejected.CONFLICT)
// Succeeded: created
else -> {
val user = User(id, email)
users[id] = user
Outcomes.success(user)
}
}
}
val outcome: Outcome<User> = userService.create("alice", "alice@example.com")
when (val status = outcome.status) {
is Succeeded -> println("created")
is Restricted -> println("not allowed: ${status.name}")
is Invalid -> println("bad input: ${status.name}")
is Rejected -> println("conflict: ${status.name}")
else -> println("failed: ${status.name}")
}Gradle (Kotlin DSL):
dependencies {
implementation("dev.kiit:kiit-result:1.0.2")
}kiit-result depends on dev.kiit:kiit-codes transitively — you don't need to add it separately.
Return an Outcome<T> (Result<T, Err>) using the builder methods:
import kiit.result.Outcome
import kiit.result.Outcomes
fun createUser(email: String): Outcome<String> =
if (email.isBlank()) Outcomes.invalid("email required") else Outcomes.success(email)Compose with map/flatMap/fold:
createUser("alice@example.com")
.map { it.uppercase() }
.onSuccess { println("registered: $it") }
.onFailure { err -> println("could not register: ${err.message}") }See samples/sample-kotlin for a runnable end-to-end Kotlin example, or
samples/sample-java for the same library used from plain Java.
Swift: not yet distributed via SPM/XCFramework, but SKIE already
gives real, compiler-enforced Swift exhaustiveness over Success/Failure. See
samples/sample-swift for a runnable example, or the
Swift Interop guide for the full picture,
including what doesn't work yet.
Result<T, E> = Success<T> | Failure<E>
Success<T>.status : Passed (from kiit-codes)
Failure<E>.status : Failed (from kiit-codes)
Result<T, E>.action : Action? (optional, both branches)
| Term | What it is |
|---|---|
Result<T, E> |
Sealed type, either Success<T> or Failure<E>. |
Success<T> |
Holds a value: T and a status: Passed. Defaults to Succeeded.SUCCESS. |
Failure<E> |
Holds an error: E and a status: Failed. Defaults to Unserved.UNEXPECTED. |
Action |
Optional context for the operation that produced/wrapped a Result. Attach via withAction(action, chain = true). |
message |
result.status.message — a convenience accessor on every Result. |
Every Result carries a status from kiit-codes' own taxonomy, not just Failure: Passed (Succeeded, Pending, Excluded, Information) or Failed (Restricted, Invalid, Rejected, Unserved). See the kiit-codes docs for the full set of groups and codes.
Option<T>, Try<T>, Outcome<T>, and Validated<T> are type aliases fixing E for common cases, each with a matching builder already wired up:
| Alias | Definition | Role |
|---|---|---|
Outcome<T> |
Result<T, Err> |
kiit-codes' Err as the error type, the most commonly used alias. |
Validated<T> |
Result<T, Err.ErrorList> |
For validation, collecting multiple errors instead of stopping at the first. |
Try<T> |
Result<T, Throwable> |
Exception as the error type. |
Option<T> |
Result<T, Unit> |
The historical Option/Maybe role, reimagined so absence carries a status. Options.some(value)/Options.none() are the entry points. |
Composition operators mirror what you'd expect from Result/Either in other languages — map, flatMap/then, fold, getOrElse, recover, and friends, plus withStatus/withAction for attaching a status/operation context after construction. See the full operator reference for the complete list.
A List<Result<T, E>> adds its own operators: combine() sequences the list into one Result<List<T>, E>, short-circuiting on the first Failure; partition() splits it into successes and errors; allSuccess/allFailure/anySuccess/anyFailure check the batch without building a new Result.
Builder<E> provides status-aware factory methods so you rarely build Success/Failure directly. Outcomes/Options/Tries/Validations are the ready-made implementations, one per alias.
| Builder | Status group | Default |
|---|---|---|
success(value) |
Passed.Succeeded |
Succeeded.SUCCESS |
pending(value) |
Passed.Pending |
Pending.ACCEPTED |
excluded(value) |
Passed.Excluded |
Excluded.OMITTED |
information(value) |
Passed.Information |
Information.NOTICE |
restricted(...) |
Failed.Restricted |
Restricted.DENIED |
invalid(...) |
Failed.Invalid |
Invalid.INVALID_VALUE |
rejected(...) |
Failed.Rejected |
Rejected.RULE_VIOLATION |
unserved(...) |
Failed.Unserved |
Unserved.UNEXPECTED |
Note that excluded() builds a Success, not a Failure — an intentionally excluded item (deduplicated, disqualified, filtered out) is a kiit-codes Passed.Excluded status, not a failure. There's no separate conflict() — it's rejected(status = Rejected.CONFLICT), since a conflict is just a specific Rejected outcome, not its own category.
See the Builders docs for the full Builder<E> architecture, Options.some/none, and worked examples for Outcomes/Options/Tries/Validations.
toOutcome() — converts any Result<T, E> to Outcome<T> (Result<T, Err>), building an Err from whatever the failure held (String, Exception, or an existing Err).toTry() — converts any Result<T, E> to Try<T> (Result<T, Throwable>). An Err-typed failure becomes a kiit-codes StatusException via Failed.toException(errors), so the exception still carries the original status and error detail.Tries.of { ... } — the reverse direction: if the block throws a StatusException (RestrictedException/InvalidException/RejectedException/UnservedException), the resulting Try is built with the matching restricted/invalid/rejected/unserved status instead of a generic failure.Outcome<T> instead of throwing for expected failures.map/flatMap chains compose without manual null/exception checks at each step.Validated<T> (Result<T, Err.ErrorList>) collects multiple errors via Validations.toTry()/Tries.of interop with StatusException when a caller only understands exceptions.result.status converts via kiit-codes' CodesToHttp/CodesToGrpc.Good fit if:
Result type layered on top of it, instead of a bespoke one.map/flatMap) without nested try/catch.Probably not necessary if:
Result wrapper — in which case see kiit-codes on its own.kiit-result has been extracted from the Kiit toolkit and polished as a standalone module. This has been used in production for over 4+ years to power mobile and server kotlin applications. Current work is focused on the Kotlin release, documentation, examples, and ecosystem integration.
| # | Topic | Description |
|---|---|---|
| 1 | npm publish | Publish pipeline for JS consumers (@kiit/result). |
| 2 | SPM / XCFramework | Distribution for Swift consumers — SKIE is already applied for real Swift exhaustiveness (see Start above and samples/sample-swift), but the actual .xcframework + SPM package, published to kiit-spm, is still unbuilt. |
| 3 | Raise<E>-style DSL |
A flat, non-nested alternative to .then { } chaining for multi-step composition (result { } + .bind()) — needs Kotlin context parameters, experimental as of 2.3.x and reaching Stable in 2.4.0. |
See GitHub Issues for current work and discussions.
| # | Topic | Description |
|---|---|---|
| 1 | Concepts | Explore Result/Success/Failure, Action, and every type alias in depth. Read the concepts docs. |
| 2 | Builders | Learn how the status-aware builders work and how to implement your own Builder<E>. Read the builders docs. |
| 3 | Conversions | See every conversion between Result, Outcome, Try, and kiit-codes' StatusException. Read the conversions docs. |
| 4 | Validation | Learn how Validated<T> and Validations accumulate multiple errors instead of stopping at the first. Read the validation docs. |
| 5 | Codes | See the separate kiit-codes module this library builds its status taxonomy on top of. Read the kiit-codes README. |
| 6 | FAQ | Answers to common questions about design, comparisons, adoption, the AI angle, and project maturity. Read the FAQ. |
| 7 | Design | Read more about the reasoning behind a status on both branches, a flexible error type, and where kiit-result fits relative to Arrow/kotlin-result. Read the design docs. |
dev.kiit:kiit-codes (transitively available to consumers via api)Contributions and design feedback are welcome. See BUILD.md for build, test, and publish instructions.
kiit-result is one module of Kiit — a lightweight, modular Kotlin toolkit for building server applications, APIs, CLIs, and jobs.
Adopt one module at a time.
A Kotlin Result<T, E> that also tells you the kind of success and failure.
Every Success and Failure carries a status classifying the kind of outcome, unlike Rust's Result, Swift's Result, or other Kotlin Result types, where success is just a bare value. A Success might be a plain success, a pending operation, or an intentional skip; a Failure might be unauthorized, invalid input, or a conflict. That status comes from kiit-codes, an already-established taxonomy reused here instead of inventing a new one. Outcome<T> is the ready-made alias for everyday use, paired with Try<T>, Option<T>, and Validated<T> for exceptions, absence, and validation.
| # | Topic | Description | |
|---|---|---|---|
| 1 | 💡 | Why | Problems kiit-result is designed to solve |
| 2 | 🚀 | Start | Installation and a quick example |
| 3 | 🧠 | Concepts |
Result/Success/Failure, Action, and the type aliases |
| 4 | 🏗️ | Builders | Status-aware factory methods for building a Result
|
| 5 | 🔁 | Conversions | Converting between Outcome, Try, and exceptions |
| 6 | ⚙️ | Usage | Use cases, and when (not) to reach for this |
| 7 | 🗺️ | Roadmap | Planned improvements and future work |
| 8 | 📖 | Learn More | Deeper documentation, design rationale, and FAQ on kiit.dev |
| 9 | 📋 | Requirements | Platforms and dependencies |
| 10 | 🤝 | Contributing | Build, test, and contribute |
| 11 | 📄 | License | Apache 2.0 license |
Most approaches capture whether something succeeded or failed, but do not have a mechanism for also indicating what the kind of success or failure it was. This is the primary gap that this library is aiming to solve.
kiit-result is a Result<T, E> with kiit-codes' closed status taxonomy built in. Success carries a Passed status, Failure carries a Failed status, and builders (restricted(), invalid(), ...) map each case to its matching category automatically. See Concepts and Builders for the full picture.
import kiit.codes.Err
import kiit.codes.Invalid
import kiit.codes.Rejected
import kiit.codes.Restricted
import kiit.codes.Succeeded
import kiit.result.Outcome
import kiit.result.Outcomes
class UserService {
private val users = mutableMapOf<String, User>()
// Alias Outcome<User> = Result<User, Err>
// Err is an error type from kiit-codes.
fun create(id: String, email: String): Outcome<User> = when {
// Restricted: a reserved id, not allowed
id == "admin" -> Outcomes.restricted(Restricted.DENIED)
// Invalid: bad input
email.isBlank() -> Outcomes.invalid(Err.on("email", email, "email is required"))
// Rejected: already exists
users.containsKey(id) -> Outcomes.rejected(Rejected.CONFLICT)
// Succeeded: created
else -> {
val user = User(id, email)
users[id] = user
Outcomes.success(user)
}
}
}
val outcome: Outcome<User> = userService.create("alice", "alice@example.com")
when (val status = outcome.status) {
is Succeeded -> println("created")
is Restricted -> println("not allowed: ${status.name}")
is Invalid -> println("bad input: ${status.name}")
is Rejected -> println("conflict: ${status.name}")
else -> println("failed: ${status.name}")
}Gradle (Kotlin DSL):
dependencies {
implementation("dev.kiit:kiit-result:1.0.2")
}kiit-result depends on dev.kiit:kiit-codes transitively — you don't need to add it separately.
Return an Outcome<T> (Result<T, Err>) using the builder methods:
import kiit.result.Outcome
import kiit.result.Outcomes
fun createUser(email: String): Outcome<String> =
if (email.isBlank()) Outcomes.invalid("email required") else Outcomes.success(email)Compose with map/flatMap/fold:
createUser("alice@example.com")
.map { it.uppercase() }
.onSuccess { println("registered: $it") }
.onFailure { err -> println("could not register: ${err.message}") }See samples/sample-kotlin for a runnable end-to-end Kotlin example, or
samples/sample-java for the same library used from plain Java.
Swift: not yet distributed via SPM/XCFramework, but SKIE already
gives real, compiler-enforced Swift exhaustiveness over Success/Failure. See
samples/sample-swift for a runnable example, or the
Swift Interop guide for the full picture,
including what doesn't work yet.
Result<T, E> = Success<T> | Failure<E>
Success<T>.status : Passed (from kiit-codes)
Failure<E>.status : Failed (from kiit-codes)
Result<T, E>.action : Action? (optional, both branches)
| Term | What it is |
|---|---|
Result<T, E> |
Sealed type, either Success<T> or Failure<E>. |
Success<T> |
Holds a value: T and a status: Passed. Defaults to Succeeded.SUCCESS. |
Failure<E> |
Holds an error: E and a status: Failed. Defaults to Unserved.UNEXPECTED. |
Action |
Optional context for the operation that produced/wrapped a Result. Attach via withAction(action, chain = true). |
message |
result.status.message — a convenience accessor on every Result. |
Every Result carries a status from kiit-codes' own taxonomy, not just Failure: Passed (Succeeded, Pending, Excluded, Information) or Failed (Restricted, Invalid, Rejected, Unserved). See the kiit-codes docs for the full set of groups and codes.
Option<T>, Try<T>, Outcome<T>, and Validated<T> are type aliases fixing E for common cases, each with a matching builder already wired up:
| Alias | Definition | Role |
|---|---|---|
Outcome<T> |
Result<T, Err> |
kiit-codes' Err as the error type, the most commonly used alias. |
Validated<T> |
Result<T, Err.ErrorList> |
For validation, collecting multiple errors instead of stopping at the first. |
Try<T> |
Result<T, Throwable> |
Exception as the error type. |
Option<T> |
Result<T, Unit> |
The historical Option/Maybe role, reimagined so absence carries a status. Options.some(value)/Options.none() are the entry points. |
Composition operators mirror what you'd expect from Result/Either in other languages — map, flatMap/then, fold, getOrElse, recover, and friends, plus withStatus/withAction for attaching a status/operation context after construction. See the full operator reference for the complete list.
A List<Result<T, E>> adds its own operators: combine() sequences the list into one Result<List<T>, E>, short-circuiting on the first Failure; partition() splits it into successes and errors; allSuccess/allFailure/anySuccess/anyFailure check the batch without building a new Result.
Builder<E> provides status-aware factory methods so you rarely build Success/Failure directly. Outcomes/Options/Tries/Validations are the ready-made implementations, one per alias.
| Builder | Status group | Default |
|---|---|---|
success(value) |
Passed.Succeeded |
Succeeded.SUCCESS |
pending(value) |
Passed.Pending |
Pending.ACCEPTED |
excluded(value) |
Passed.Excluded |
Excluded.OMITTED |
information(value) |
Passed.Information |
Information.NOTICE |
restricted(...) |
Failed.Restricted |
Restricted.DENIED |
invalid(...) |
Failed.Invalid |
Invalid.INVALID_VALUE |
rejected(...) |
Failed.Rejected |
Rejected.RULE_VIOLATION |
unserved(...) |
Failed.Unserved |
Unserved.UNEXPECTED |
Note that excluded() builds a Success, not a Failure — an intentionally excluded item (deduplicated, disqualified, filtered out) is a kiit-codes Passed.Excluded status, not a failure. There's no separate conflict() — it's rejected(status = Rejected.CONFLICT), since a conflict is just a specific Rejected outcome, not its own category.
See the Builders docs for the full Builder<E> architecture, Options.some/none, and worked examples for Outcomes/Options/Tries/Validations.
toOutcome() — converts any Result<T, E> to Outcome<T> (Result<T, Err>), building an Err from whatever the failure held (String, Exception, or an existing Err).toTry() — converts any Result<T, E> to Try<T> (Result<T, Throwable>). An Err-typed failure becomes a kiit-codes StatusException via Failed.toException(errors), so the exception still carries the original status and error detail.Tries.of { ... } — the reverse direction: if the block throws a StatusException (RestrictedException/InvalidException/RejectedException/UnservedException), the resulting Try is built with the matching restricted/invalid/rejected/unserved status instead of a generic failure.Outcome<T> instead of throwing for expected failures.map/flatMap chains compose without manual null/exception checks at each step.Validated<T> (Result<T, Err.ErrorList>) collects multiple errors via Validations.toTry()/Tries.of interop with StatusException when a caller only understands exceptions.result.status converts via kiit-codes' CodesToHttp/CodesToGrpc.Good fit if:
Result type layered on top of it, instead of a bespoke one.map/flatMap) without nested try/catch.Probably not necessary if:
Result wrapper — in which case see kiit-codes on its own.kiit-result has been extracted from the Kiit toolkit and polished as a standalone module. This has been used in production for over 4+ years to power mobile and server kotlin applications. Current work is focused on the Kotlin release, documentation, examples, and ecosystem integration.
| # | Topic | Description |
|---|---|---|
| 1 | npm publish | Publish pipeline for JS consumers (@kiit/result). |
| 2 | SPM / XCFramework | Distribution for Swift consumers — SKIE is already applied for real Swift exhaustiveness (see Start above and samples/sample-swift), but the actual .xcframework + SPM package, published to kiit-spm, is still unbuilt. |
| 3 | Raise<E>-style DSL |
A flat, non-nested alternative to .then { } chaining for multi-step composition (result { } + .bind()) — needs Kotlin context parameters, experimental as of 2.3.x and reaching Stable in 2.4.0. |
See GitHub Issues for current work and discussions.
| # | Topic | Description |
|---|---|---|
| 1 | Concepts | Explore Result/Success/Failure, Action, and every type alias in depth. Read the concepts docs. |
| 2 | Builders | Learn how the status-aware builders work and how to implement your own Builder<E>. Read the builders docs. |
| 3 | Conversions | See every conversion between Result, Outcome, Try, and kiit-codes' StatusException. Read the conversions docs. |
| 4 | Validation | Learn how Validated<T> and Validations accumulate multiple errors instead of stopping at the first. Read the validation docs. |
| 5 | Codes | See the separate kiit-codes module this library builds its status taxonomy on top of. Read the kiit-codes README. |
| 6 | FAQ | Answers to common questions about design, comparisons, adoption, the AI angle, and project maturity. Read the FAQ. |
| 7 | Design | Read more about the reasoning behind a status on both branches, a flexible error type, and where kiit-result fits relative to Arrow/kotlin-result. Read the design docs. |
dev.kiit:kiit-codes (transitively available to consumers via api)Contributions and design feedback are welcome. See BUILD.md for build, test, and publish instructions.
kiit-result is one module of Kiit — a lightweight, modular Kotlin toolkit for building server applications, APIs, CLIs, and jobs.
Adopt one module at a time.