Finite state machines for Java
Your workflow mentor — from start to finish
Finito models a workflow as states and the events that move between them. Define it in XML, a fluent Java API, or JSON; add actions, hooks and persistence by convention; and get a Mermaid & PlantUML diagram for free.
Your entity carries its own state; one call — STM.proceed(entity, event, payload) — advances it, running every action, guard and hook along the way.
Manual states wait for an event. Auto states decide their own next move by evaluating a condition on the data.
An <on> maps an eventId to a newStateId and an optional action. That's the whole grammar of movement.
Your object extends StateEntity and carries its current state. Finito persists it for you and can fire hooks after every save.
XML is convenient and reviewable — but it's only one door. Build a flow with a fluent Java API, or load it from JSON. Same engine, same diagrams, your choice of source.
<states>
<flow id='returns-flow' default='true'>
<entry-action componentName="returnsEntryAction"/>
<manual-state id='REQUESTED' initialState='true'>
<on eventId='approve' newStateId='APPROVED' componentName='ApproveReturn'/>
<on eventId='reject' newStateId='REJECTED'/>
</manual-state>
<auto-state id='INSPECTION' code='payload.condition'>
<on eventId='ok' newStateId='REFUNDED'/>
<on eventId='damaged' newStateId='REJECTED'/>
</auto-state>
</flow>
</states>
new FluentFlowReader(store)
.newFlow("returns-flow").makeDefault()
.entryAction(new ReturnsEntryAction())
.manualState("REQUESTED", true)
.on("approve").transitionAction(new ApproveReturn()).transitionTo("APPROVED").state()
.on("reject").transitionTo("REJECTED").state()
.flow()
.manualState("APPROVED")
.on("ship").transitionTo("LABEL_SENT").state()
.flow()
.autoState("INSPECTION")
.component(new IfAction<>())
.property("condition", "payload.condition")
.on("ok").transitionTo("REFUNDED").state()
.on("damaged").transitionTo("REJECTED").state()
.flow()
.manualState("REFUNDED");
{
"id": "returns-flow",
"defaultFlow": true,
"states": [
{ "id": "REQUESTED", "manualState": true, "initialState": true,
"transitions": [
{ "eventId": "approve", "newStateId": "APPROVED", "componentName": "ApproveReturn" },
{ "eventId": "reject", "newStateId": "REJECTED" }
]},
{ "id": "INSPECTION", "automaticState": true, "code": "payload.condition",
"transitions": [
{ "eventId": "ok", "newStateId": "REFUNDED" },
{ "eventId": "damaged", "newStateId": "REJECTED" }
]}
]
}
STMFlowStore. Pick whichever fits — generate flows programmatically with the fluent API, ship them as JSON from a config service, or keep them as reviewable XML.However you defined it, this is the machine. Below it animates: an entity walks its states as events fire; the auto state routes itself. Under that, the same flow as a static reference.
REQUESTED also goes to REJECTED on reject. The auto state INSPECTION evaluates the item's condition and routes itself — no event from the caller.
A single transition can run a transition action, land in a new state whose entry action fires, persist the entity automatically, and trigger post-save hooks. You declare it; Finito orchestrates it.
proceed(entity, "approve")
runs your business logic
state entered · SLA/timestamps set
entityStore.store() → Spring repo
notify · integrate · audit
Attach a component to an <on> to run code when an event fires — validate, call a service, mutate the entity. It implements STMTransitionAction.
Run logic as a state is entered or left. The built-in GenericEntryAction stamps state-entry time and SLA fields, then persists — so timing and storage are automatic.
An auto-state evaluates an OGNL expression on the entity and picks its own transition — decisions live in the flow, resolved by an STMAutomaticStateComputation.
PostSaveHook.execute(start, end, entity, payload) fires after the entity is stored on a transition — the safe place to publish events, notify or integrate.
State is saved through an EntityStore that wraps your Spring repository — entering a state stores the entity with no explicit save call in your code.
Attach a security-strategy with meta-acls permissions to events, or an enablement-strategy to switch transitions on and off by configuration.
You rarely wire components by hand. Finito's resolver finds your transition actions, auto-state computations and post-save hooks in the Spring context (or any bean factory) using a naming convention built from a prefix, the event or state, and a type suffix.
// STMTransitionActionResolver
beanName = prefix + Capitalize(eventId) + suffix
// suffix by component type
TRANSITION_ACTION -> "Action"
POST_SAVE_HOOK -> "PostSaveHook"
AUTO_STATE -> "AutoState"
// for a "returns" workflow, event "approve":
returnsApproveAction // transition action
returnsAssignedPostSaveHook // hook on the ASSIGNED state
returnsInspectionAutoState // auto-state computation
prefix + Entity + Event), then falls back to prefix + Event.x-tenant selects a tenant-specific bean, enabling per-tenant actions and hooks.returns service, the resolver is configured with the workflow prefix; dropping a Spring bean named returnsApproveAction (or a tenant-scoped variant) is all it takes to hook business logic onto the approve event. The same convention discovers post-save hooks and auto-state computations.Finito reads the flow and emits state diagrams in both Mermaid and PlantUML. Main-path, auto and orphaned states are colour-coded automatically, so the picture always matches the running machine.
XML · fluent · JSON
StmPumlGenerator · STMMermaidGenerator
stateDiagram-v2 · renders on the web
.puml · for docs & CI
stateDiagram-v2
classDef mainPath fill:Bisque,stroke:Peru,stroke-width:4px;
classDef autoState fill:PaleGreen,stroke:green,stroke-width:2.5px;
[*] --> REQUESTED
REQUESTED --> APPROVED : approve
REQUESTED --> REJECTED : reject
APPROVED --> LABEL_SENT : ship
LABEL_SENT --> INSPECTION : receive
state INSPECTION <<choice>>
INSPECTION --> REFUNDED : ok
INSPECTION --> REJECTED : damaged
class REQUESTED mainPath
class INSPECTION autoState@startuml
[*] --> REQUESTED
REQUESTED --> APPROVED : approve
REQUESTED --> REJECTED : reject
APPROVED --> LABEL_SENT : ship
LABEL_SENT --> INSPECTION : receive
state INSPECTION <<choice>>
INSPECTION --> REFUNDED : ok
INSPECTION --> REJECTED : damaged
@endumlLive Mermaid render of the code above ↓
stateDiagram-v2 [*] --> REQUESTED REQUESTED --> APPROVED : approve REQUESTED --> REJECTED : reject APPROVED --> LABEL_SENT : ship LABEL_SENT --> INSPECTION : receive state INSPECTION <<choice>> INSPECTION --> REFUNDED : ok INSPECTION --> REJECTED : damaged
A trajectory is an ephemeral, per-request variant selected by the header x-chenile-trajectory-id. Because Finito resolves its transition actions and auto-states by convention, a trajectory can point an event at a different action or auto-state computation — no if statements, nothing to untangle later.
— no trajectory header —@ConditionalOnTrajectory) and returns different configuration values — read the Trajectories chapter on chenile.org.Finito ships an inbuilt blueprint — bp-wfcustom — that jgen expands into a complete Chenile service: an -api and a -service module wired to your state machine, with persistence, tests and diagrams.
+ a few answers
the inbuilt blueprint
StateEntity + StateEntityService<T>
actions, hooks, config, BDD tests
Mermaid + PlantUML
AbstractStateEntity; the generated API reuses the shared StateEntityService<T> — so every workflow exposes the same verbs: process, processById, create, retrieve, getAllowedActionsAndMetadata. Toggle JPA, security, activities, enablement and the cloud switch at generation time.stm-cli works straight off your flow — render its diagram, and even scaffold test cases that walk every transition.
# render a Mermaid state diagram
stm-cli --mermaid returns-states.xml
# generate test cases that walk every transition
stm-cli --generate-test-cases returns-states.xml
# render a state diagram for each generated test path
stm-cli --render-tests-as-state returns-states.xml
-m, --mermaidEmit a Mermaid-compatible state diagram for the flow.
-t, --generate-test-casesGenerate test cases that exercise the machine's transitions.
-r, --render-tests-as-stateDraw a state diagram for every generated test-case path.
Finito is the state-machine core of the Chenile framework, but the engine has no framework strings attached.
Drop org.chenile.stm into any Java app. Build a flow with XmlFlowReader, the FluentFlowReader API, or from JSON, then drive entities with STM.proceed(...).
Use the full workflow blueprint and get a first-class Chenile service: convention-wired actions and hooks, Spring persistence, security, activities, BDD tests and REST endpoints.
-api + -service via jgen bp-wfcustomDefine a flow — in XML, Java or JSON — drive it with one call, let Finito wire and persist it, and draw the diagram for you.