Finite State Machine · JVM

Your workflow is a subway map.
Hydra only takes the legal rides.

A tiny, thread-safe, type-safe library with a fluent DSL for finite state machines on the JVM — usable from Java and Kotlin. Declare the states your system can be in and the events that move between them. Hydra makes only the legal moves, models every outcome as a type, and emits an action on each transition — built for the hard case: driving long-running asynchronous orchestrators.

v0.1.1 · Apache-2.0 · JDK 21+ · Java & Kotlin · a Mealy machine

Order Line in service
#Arrives atVia eventEmits action
  1. 01 Placed Checkout ChargeCard
  2. 02 Shipped PaymentSucceeded ShipParcel
  3. 03 Cancelled PaymentFailed no action
  4. 04 Cancelled Cancel RefundCard
Every push returns one of Valid·Invalid·NoFromState — illegal moves rejected by construction.

// why a state machine

Stations, turnstiles, one ride at a time.

Stations are states; the turnstiles between them are events — you can't teleport between any two stations, only take the connections the map allows. Modelling a workflow this way, instead of a tangle of if/else and callbacks, buys three things that matter at scale.

  1. 01

    Legal moves are explicit

    Illegal transitions are rejected by construction, not discovered at runtime. The map is the contract.

  2. 02

    Failures are types

    Every push returns a sealed TransitionValid, Invalid, or NoFromState. The caller is forced to handle "this can't happen".

  3. 03

    Durable resume

    Because the position is an explicit value, a run that dies mid-flight knows exactly which step it was at — and resumes from there.

// the order machine, as a line map

Read the whole machine at a glance.

Each track is one transition. Its colour is the station it leads to; the label reads Event (the input you push in) over Action (the command emitted out). A dashed track emits no action.

Checkout ChargeCard PaymentSucceeded ShipParcel PaymentFailed emits nothing Cancel RefundCard START END Cart Placed Shipped Cancelled LEGEND Ring = State — where the order is Bold = Event — the input you push in Mono = Action — the command emitted out Dashed = a transition that emits no action Hydra.create( … ) — the Order machine wired up in OrderDomain.java & HydraTest.java
— · the position

State

Where the machine is — a discrete station, held one at a time. May carry context: Placed(amount, address).

input ↓ · the cause

Event

Something that happened to the machine. You push it in — often a bare signal: PaymentSucceeded.

output ↑ · the effect

Action

A command the machine emits on a transition. You receive it and do the work: ShipParcel(address).

// two ways to drive the line

In-memory, or as a pure routing function.

A plain transition(event) is perfect for a synchronous, single-process machine. But Hydra was built for the harder case — where the durable truth lives outside the JVM.

One ride at a time. Only the legal ones.

Declare your states and events; let the compiler keep them straight.

Hydra.create(mb -> )