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.Real business workflows need checklists, feature toggles, permissions, introspection and their own vocabulary. Finito builds all of these into the same declarative flow.
Tag transitions as mandatory or optional activities (meta-activity="MANDATORY"). An automatic activity-checker state (e.g. AreClosedActivitiesComplete) blocks progress until every mandatory activity for a status is done — so a workflow can't advance with work outstanding.
Attach an enablement-strategy (e.g. a config-based one) to switch transitions on and off by configuration — feature flags for your workflow, per module, tenant or deployment, without touching the XML.
Add a security-strategy and attach meta-acls permissions to individual events. The permission is checked before the action runs — authorization lives in the flow, next to the transition it guards.
Generated workflow services expose an admin info API under /{service}/info/… — render the live state diagram, list the allowed actions for an entity, return the workflow as JSON, and fetch generated test cases.
Define your own vocabulary with add-state-tag / add-transition-tag. A custom <status> or <on-action> tag expands into the underlying states and transitions — so flows read in your domain language.
Entry actions stamp state-entry time and compute SLA fields (late / getting-late) from the flow metadata, so dashboards and escalations have the timing data they need without extra code.
<!-- define the vocabulary once… -->
<add-state-tag tag="status" manualState="true"/>
<add-transition-tag tag="on-action" eventIdTag="id" newStateIdTag="move-to"/>
<add-transition-tag tag="mandatory-activity" meta-activity="MANDATORY" eventIdTag="id"/>
<!-- …then write the flow in your own words -->
<status id='OPENED' initialState='true'>
<on-action id='assign' move-to='ASSIGNED'/>
<mandatory-activity id='attachDocuments'/>
</status>
These features are generated and wired by the wfcustom blueprint. In the full Chenile framework they compose with security, multi-tenancy and messaging.
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.A generated workflow service exposes three verbs. Every event goes through the same generic engine; your code is found by name, gets a strongly typed payload, and the response tells the caller what it may do next.
// create — starts in the initial state
POST /returns { "openedBy": "USER1" }
// retrieve
GET /returns/{id}
// fire an event — the payload type depends on the event
PATCH /returns/{id}/approve { "comment": "looks good" }
// every call answers with
{ "payload": {
"mutatedEntity": { …, "currentState": { "stateId": "APPROVED" } },
"allowedActionsAndMetadata": [
{ "allowedAction": "refund", "acls": "returns.refund" },
{ "allowedAction": "inspect", "activity": "MANDATORY" } ] } }
PATCH …/approveStmBodyTypeSelector picks the payload type — explicit bodyType metadata, or the second parameter of the action's transitionTo(…).BaseTransitionAction runs a metadata-named command or orchestration if there is one…returnsApproveAction and runs your typed AbstractSTMTransitionAction.approve (SecondSTMTransitionAction) run next, in order.GenericEntryAction stamps SLA timing, stores the entity, and calls the state's post-save hook.Every meta-* attribute on a transition comes back on its allowed action with the prefix stripped — meta-acls → acls, meta-label → label. Build buttons from it; never hard-code events.
/{svc}/info/state-diagram, allowed-actions?state=, json, test-cases, test-visualization and test-state-diagrams expose the flow itself to consoles and tools.
With workflow-mcp, AI agents see each event's payload variant (ProcessIdPolymorph). Workflow-backed Chenile queries return the same allowedActions per row.
stmcmds class and the security wiring are on chenile.org: workflow service internals.Chenile Process Management orchestrates tree-shaped batch work — split a job, run the pieces, aggregate the results, chain the next job — and the orchestrator itself is a Finito state machine. Processes are rows in your database; workers raise events back into the flow.
isThisLeafNode ── leaf ──────▶ EXECUTING ── doneSuccessfully ──▶ PROCESSED
│ └─ doneWithErrors ──▶ PROCESSED_WITH_ERRORS
└─ non-leaf ─▶ SPLIT_PENDING ── splitDone ─▶ SUB_PROCESSES_PENDING
SUB_PROCESSES_PENDING ── child done ─▶ areAllSubProcessesDone?
numSubProcesses == numCompletedSubProcesses && splitCompleted
─▶ AGGREGATION_PENDING ── aggregationDone ─▶ areThereErrors?
─▶ PROCESSED | PROCESSED_WITH_ERRORS
isThisLeafNode, areAllSubProcessesDone, areThereErrors) keep the coordination rules in the flow, not in code.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.