What it is
Arrow brings typed functional programming to Kotlin: Either and Option for errors, typed error DSLs, optics, and resource-safe operations.
Either<Error, Value> makes failure part of the return type. The raise DSL lets you write straight-line code that short-circuits on error without exceptions.
Installation
implementation("io.arrow-kt:arrow-core:1.2.4")Getting started
The smallest useful thing you can do with it, and what each part means.
kotlin
sealed interface BookError {
data object NotFound : BookError
data class Invalid(val reason: String) : BookError
}
// The raise DSL reads sequentially but short-circuits on the first failure.
fun createBook(input: BookInput): Either<BookError, Book> = either {
ensure(input.title.isNotBlank()) { BookError.Invalid("title required") }
ensure(input.year in 1400..2100) { BookError.Invalid("year out of range") }
val author = findAuthor(input.authorId).bind() // unwraps or raises
Book(input.title, input.year, author)
}
when (val result = createBook(input)) {
is Either.Right -> respond(201, result.value)
is Either.Left -> respond(400, result.value)
}Advanced usage
Where the library earns its place over a simpler alternative.
kotlin
// Report every validation failure, not just the first.
fun validate(input: BookInput): EitherNel<String, Book> = either {
zipOrAccumulate(
{ ensure(input.title.isNotBlank()) { "title required" }; input.title },
{ ensure(input.year > 1400) { "year too early" }; input.year },
{ ensureNotNull(input.authorId) { "author required" } },
) { title, year, authorId -> Book(title, year, authorId) }
}
// Guaranteed release, even on cancellation.
resourceScope {
val conn = install({ openConnection() }) { c, _ -> c.close() }
useConnection(conn)
}Errors and fixes
The failures you are most likely to hit, and what actually resolves them.
- bind() is not available
- It only exists inside an either { } or raise-capable block. Wrap the code in either { }.
- Exceptions still escape
- Arrow does not intercept throws. Wrap risky calls in Either.catch to bring them into the typed world.
Best practices
- Start with arrow-core's Either and the raise DSL; the rest of Arrow is optional.
- Use zipOrAccumulate for validation where every error should be reported.
- Model errors as a sealed interface so handling is exhaustive.
- Introduce it in one layer rather than converting a whole codebase at once.
Background
Why it exists, and what it was reacting to.
Arrow adapted concepts from Scala's cats and Haskell to Kotlin, and has since converged on a pragmatic core — the typed error handling in arrow-core is what most teams actually adopt.
