Architecture
Design Principles
- Stateless engine — the engine takes a workflow definition and instance state as input, returns updated state as output. No persistence, no background threads, no framework dependencies.
- Consumer handles persistence — the workflow instance is a single JSON document. The consuming application stores it however it chooses (database column, file, etc.).
- Explicit wiring — all dependencies (executors, listeners, error handler) are passed via constructor. No CDI, no service discovery, no classpath scanning.
- Immutable state — all engine methods return new
WorkflowInstanceobjects. Input instances are never mutated. - One expression language — Jakarta EL is used consistently for edge conditions and event correlation.
Engine Internals
WorkflowEngine
The central class. Three categories of methods:
| Category | Methods |
|---|---|
| Lifecycle | startWorkflow, completeCurrentNode, cancelWorkflow |
| Correlation | matchesEvent |
| Expression | resolveExpression |
| Introspection | getHumanTaskInfo, getReceiveEventInfo, getWaitInfo |
| (Internal) | advance, executeActionNode, selectEdge |
Transition Loop
The advance() method is the core execution loop:
Node completes
→ fire onNodeCompleted
→ merge output into context
→ get outgoing edges (sorted by priority)
→ evaluate each condition (Jakarta EL)
→ select first match (or default)
→ fire onEdgeFollowed
→ record in history
→ fire onNodeEntered
→ execute target node:
ACTION → invoke executor, loop back
HUMAN_TASK → set WAITING, return
RECEIVE → set WAITING, return
END → set COMPLETED, return
A safety limit of 100 transitions per call prevents infinite loops.
Condition Evaluation
The ConditionEvaluator wraps Jakarta EL. Two modes:
| Context | Root Variables | Used For |
|---|---|---|
| Edge conditions | context |
Routing decisions after node completion |
| Event matching | context, event |
Correlating external events to waiting nodes |
Null or blank conditions evaluate to true (unconditional edges always match).
Validation
The WorkflowValidator runs 27 rules across four categories:
- Structural (10 rules) — graph integrity (start/end nodes, edge references, duplicates)
- Connectivity (5 rules) — reachability, dead ends, isolated nodes
- Edge/Condition (4 rules) — default edges, duplicate priorities, EL syntax
- Semantic (8 rules) — event receivers, action types, input schemas, wait durations, automated cycles
startWorkflow runs the validator automatically and rejects definitions with ERROR-level problems.
UI Internals
Component Hierarchy
WorkflowEditor (ReactFlowProvider wrapper)
└─ WorkflowEditorInner
├─ NodePalette (drag source for new nodes)
├─ ReactFlow canvas
│ ├─ Custom node components (5 types)
│ └─ ConditionalEdge component
├─ PropertiesPanel (node/edge config form)
└─ ProblemsPanel (validation results)
WorkflowViewer (ReactFlowProvider wrapper)
└─ WorkflowViewerInner
└─ ReactFlow canvas (read-only, styled by instance state)
Data Flow
The editor maintains internal React Flow state (useNodesState, useEdgesState) and converts between the Workflow model and React Flow's Node[]/Edge[] model using adapter functions in utils/conversion.ts.
Workflow (prop) → toReactFlowNodes/Edges → React Flow state
↓ (on change)
toWorkflow → onChange callback
↓
validateWorkflow → onValidationChange
TypeScript Validator
A port of the Java WorkflowValidator implementing 23 of 24 rules (skips INVALID_CONDITION which requires Jakarta EL). Runs synchronously on every edit via useMemo.