Skip to content

Arrow

Developer UtilitiesUtilityKotlin

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.

Either and the raise DSL
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)
}
bind() unwraps an Either or aborts the whole block with its error. The result reads like ordinary sequential code while keeping failures in the type signature.

Advanced usage

Where the library earns its place over a simpler alternative.

Accumulating errors and resource safety
// 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)
}
zipOrAccumulate is the right tool for form validation: a user wants all four problems listed at once, not one per round trip.

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.