Where to Start

"Would you tell me, please, which way I ought to go from here?"

"That depends a good deal on where you want to get to," said the Cat.

-- Lewis Carroll, Alice's Adventures in Wonderland

Alice's reply was that she did not much care where, at which point the Cat told her any road would do. This page is built on the opposite assumption: that you do care, that you already have a problem in front of you, and that what you need is the shortest route to the chapter that solves it.

What You'll Learn

  • Which tool fits the problem you have, before you know the library's vocabulary
  • How the four tool families combine when one is not enough
  • Where to go next once you have picked your direction

The one question

What are you actually trying to do?

Pick the branch that matches. If two match, read both and combine them; the Combining tools section below covers the common overlaps.

  1. I'm working with values that might fail or be absent
  2. I'm reading or updating data inside immutable records
  3. I'm doing concurrent, async, or deferred I/O
  4. I'm sequencing several effects together
  5. I'm writing library or polymorphic code that others will compose with

1. Values that might fail or be absent

You have a function that may return nothing, throw, or report a domain error. You want chaining, short-circuiting, and a clear extraction point.

Your situationReach for
Value may be missing; the reason does not matterMaybePath
Bridging to Java's OptionalOptionalPath
Failure carries a typed domain error you controlEitherPath
Failure comes from code that throws exceptionsTryPath
You want all validation errors, not just the firstValidationPath

If you are not sure between EitherPath and TryPath, ask: would I want to unit-test the error branch? If yes, EitherPath. If the error is genuinely exceptional (file system blew up), TryPath is honest about it.

Go to: Effect Path Quickstart · Core Paths · full Path decision tree


2. Reading or updating nested data

You have records (often deeply nested), sealed hierarchies, or collections, and you want to read or update one field without rebuilding the surrounding structure by hand.

Your situationReach for
Required field on a record, get/setLens, via Focus DSL
Optional field, or a field that may be absentAffine / AffinePath
One variant of a sealed typePrism
Every element of a collectionTraversal
Two equivalent representationsIso
The type isn't yours to annotate@ImportOptics or an OpticsSpec

In practice almost everyone starts with the Focus DSL and annotations like @GenerateLenses, @GenerateFocus, @GeneratePrisms; hand-written optic composition is rarely needed.

Go to: Optics Quickstart · full Optics decision trees


3. Concurrent, async, or deferred I/O

You are calling APIs, talking to a database, reading files, fanning out work across threads, and you want a sane composition story.

Your situationReach for
Defer a side effect; do not run it until you askIOPath
Concurrent work, virtual threads, structured concurrencyVTaskPath
Lazy pull-based streaming, possibly infiniteVStreamPath
Existing CompletableFuture<T> returns you must integrate withCompletableFuturePath
Memoise a deferred computationLazyPath
Deeply recursive algorithm that would blow the stackTrampolinePath

If you are starting a new service from scratch on Java 21+, default to VTaskPath; it gives you virtual-thread concurrency without forcing the rest of your code reactive.

Go to: VTask Monad · Structured Concurrency


4. Sequencing several effects

You have more than one of the above to do, in order. Two answers, depending on how regular the pipeline is.

Your situationReach for
A linear pipeline: do A, then B with A's result, then CThe Path's own .flatMap / .map chain
A pipeline that pulls from several independent sourcesForPath comprehension
Independent steps you want to run in parallelForPath parallel
The same operation across a collectionForPath traverse
A DSL you want to interpret in multiple ways (prod, audit, dry-run)FreePath and Effect Handlers

ForPath is the answer most of the time once a pipeline has more than two steps and one of them needs a name. It reads top-to-bottom and removes the nesting that flatMap chains accumulate.

Go to: For Comprehension · Composition Patterns


5. Library or polymorphic code

You are not writing application code; you are writing a function that other teams will compose into their effect stack. The Path API fixes the outer effect at the call site, which is wrong for this case.

Your situationReach for
You need to read configuration without fixing the caller's effectMonadReader
You need to read and modify state polymorphicallyMonadState
You need to record an audit log polymorphicallyMonadWriter
You need to combine several of the aboveMTL Combining Capabilities
Bridging to a third-party type like Mono<Either<E, A>>EitherT directly

If you are not in one of these cases, you do not need the transformer chapter. The full reasoning lives in Path or Transformer?.

Go to: MTL Capabilities · Transformers Quickstart


Combining tools

The four families overlap. Here are the five combinations that come up most often in real code, each with the right entry point.

Validation and nested update

You need to update a field inside a record, but the update can fail.

Async and typed errors

You need to call an async API that can return a domain error before the result is materialised.

  • Tools: VTaskPath for the async, EitherPath for the error, composed through ForPath. For polymorphic code or third-party Mono<Either<...>> shapes, drop to EitherT<VTaskKind.Witness, E, A> (EitherT).
  • Read: Composition Patterns · Path or Transformer?

Async and resilience (retry, circuit breaker, bulkhead, saga)

You have async or deferred work and you need to make it survive partial failure.

Pure logic and I/O at the edges

You want a functional core (pure, testable, no effects) and an imperative shell that runs the effects.

A DSL and multiple interpreters

You want to describe a workflow once and run it differently in production, tests, audits, or dry-runs.


Escape hatches

If none of the above fits, you are in one of these situations:


Quick reference

If you're doing this...Start here
Absent valuesMaybePath
Typed domain errorsEitherPath
Wrapping throwing codeTryPath
Accumulating validation errorsValidationPath
Deferred side effectIOPath
Async on virtual threadsVTaskPath
Lazy streamingVStreamPath
Reading or updating one nested fieldFocus DSL
Sealed-type variantPrism
Every element of a collectionTraversal
Linear pipeline.flatMap on the Path
Pipeline pulling from several sourcesForPath
Validation + nested updateOptics Integration
Async + typed errorsComposition Patterns
Resilience around async workResilience Patterns
Polymorphic library functionMTL Capabilities
Third-party outer effectEitherT
DSL with multiple interpretersFreePath + Effect Handlers

Still not sure?

Start with the Quickstart. Most production workflows use one or two Paths and a Focus DSL, and that combination is enough to ship. Reach for transformers, MTL, or Free monads only when one of the signals on this page actually fires.


Previous: Cheat Sheet Next: Effect Path API