Backend Development 13 min read

Deep Dive into Spring StateMachine: Architecture, Startup, Event Handling, and Persistence

This article provides an in‑depth technical analysis of Spring StateMachine's internal architecture, covering its class hierarchy, lifecycle start/stop mechanisms, pseudo‑state types, trigger processing, event dispatching, and persistence strategies, all illustrated with concrete code examples.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Deep Dive into Spring StateMachine: Architecture, Startup, Event Handling, and Persistence

This article continues from the previous "Spring StateMachine Basics and Practice" post and examines the internal implementation of the single‑node version of Spring StateMachine, focusing on class structure, lifecycle management, trigger handling, pseudo‑states, event processing, and persistence.

The core implementation classes are ObjectStateMachine and DistributedStateMachine ; the article mainly discusses the single‑node version. StateMachine extends the Region interface, allowing multiple parallel sub‑machines. The class diagram (omitted) shows how StateMachine inherits basic start/stop/event methods from Region and adds APIs for state handling, exception processing, and access.

Startup and Stop

Spring StateMachine’s start and stop operations are defined by the org.springframework.context.Lifecycle interface. The start call ultimately invokes org.springframework.statemachine.support.LifecycleObjectSupport#start and follows this chain:

stateMachine.start() → org.springframework.statemachine.support.LifecycleObjectSupport#start → org.springframework.statemachine.support.StateMachineObjectSupport#doStart → org.springframework.statemachine.support.LifecycleObjectSupport#doStart → org.springframework.statemachine.support.AbstractStateMachine#doStart
@Override
public final void start() {
  this.lifecycleLock.lock();
  try {
    if (!this.running) {
      this.running = true;
      this.doStart();
      // omitted ..
    }
  } finally {
    this.lifecycleLock.unlock();
  }
}

The StateMachineObjectSupport#start method registers annotation‑driven handlers (e.g., @OnTransition ) and prepares the state machine for execution, while AbstractStateMachine#start configures pseudo‑state listeners, initial‑state handling, and finally starts the StateMachineExecutor .

Pseudo‑States

Pseudo‑states such as Choice , Fork , and Join represent transient vertices that enable conditional branching and parallel execution paths. The article shows the source of StateMachineObjectSupport#doStart where these listeners are registered.

@Override
protected void doStart() {
  // org.springframework.statemachine.support.LifecycleObjectSupport#doStart
  super.doStart();
  if (!handlersInitialized) {
    try {
      stateMachineHandlerCallHelper.setBeanFactory(getBeanFactory());
      // generate handlers for annotations in StateMachineEventConfig
      stateMachineHandlerCallHelper.afterPropertiesSet();
    } catch (Exception e) {
      log.error("Unable to initialize annotation handlers", e);
    } finally {
      handlersInitialized = true;
    }
  }
}

Triggers

Two trigger types exist: EventTrigger (event‑driven) and TimerTrigger (time‑driven). The executor’s startTriggers method activates them, and execute launches a single‑threaded consumer that processes the event queue.

try {
  // mark whether an event was processed
  boolean eventProcessed = false;
  while (processEventQueue()) {
    eventProcessed = true;
    processTriggerQueue();
    while (processDeferList()) {
      processTriggerQueue();
    }
  }
  if (!eventProcessed) {
    processTriggerQueue();
    while (processDeferList()) {
      processTriggerQueue();
    }
  }
  if (requestTask.getAndSet(false)) {
    scheduleEventQueueProcessing();
  }
  taskRef.set(null);
}

The processEventQueue method pulls messages from the internal queue, checks for deferrable states, and enqueues matching transitions for execution.

Message
queuedEvent = eventQueue.poll();
State
currentState = stateMachine.getState();
if (queuedEvent != null) {
  if ((currentState != null && currentState.shouldDefer(queuedEvent))) {
    log.info("Current state " + currentState + " deferred event " + queuedEvent);
    queueDeferredEvent(queuedEvent);
    return true;
  }
  for (Transition
transition : transitions) {
    State
source = transition.getSource();
    Trigger
trigger = transition.getTrigger();
    if (StateMachineUtils.containsAtleastOne(source.getIds(), currentState.getIds())) {
      if (trigger != null && trigger.evaluate(new DefaultTriggerContext<>(queuedEvent.getPayload()))) {
        queueTrigger(trigger, queuedEvent);
        return true;
      }
    }
  }
  return true;
}
return false;

Event Sending

The entry point for sending events is AbstractStateMachine.sendEventInternal . It checks for machine errors, runs interceptors, verifies running state, and finally delegates to acceptEvent , which iterates over all transitions, evaluates guards, and queues the event for the executor.

private boolean sendEventInternal(Message
event) {
  if (hasStateMachineError()) {
    notifyEventNotAccepted(buildStateContext(Stage.EVENT_NOT_ACCEPTED, event, null, getRelayStateMachine(), getState(), null));
    return false;
  }
  try {
    event = getStateMachineInterceptors().preEvent(event, this);
  } catch (Exception e) {
    log.info("Event " + event + " threw exception in interceptors, not accepting event");
    notifyEventNotAccepted(buildStateContext(Stage.EVENT_NOT_ACCEPTED, event, null, getRelayStateMachine(), getState(), null));
    return false;
  }
  if (isComplete() || !isRunning()) {
    notifyEventNotAccepted(buildStateContext(Stage.EVENT_NOT_ACCEPTED, event, null, getRelayStateMachine(), getState(), null));
    return false;
  }
  boolean accepted = acceptEvent(event);
  stateMachineExecutor.execute();
  if (!accepted) {
    notifyEventNotAccepted(buildStateContext(Stage.EVENT_NOT_ACCEPTED, event, null, getRelayStateMachine(), getState(), null));
  }
  return accepted;
}

Persistence

Spring StateMachine provides two persistence abstractions: StateMachinePersist (writes/reads a StateMachineContext ) and StateMachinePersister (high‑level persist / restore that delegate to a StateMachinePersist implementation). The default serialization uses Kryo via KryoStateMachineSerialisationService .

@Override
public final void persist(StateMachine
stateMachine, T contextObj) throws Exception {
  // delegate to StateMachinePersist.write
  stateMachinePersist.write(buildStateMachineContext(stateMachine), contextObj);
}

@Override
public final StateMachine
restore(StateMachine
stateMachine, T contextObj) throws Exception {
  // delegate to StateMachinePersist.read
  final StateMachineContext
context = stateMachinePersist.read(contextObj);
  // omitted other code ...
}

In summary, the Spring StateMachine core is not overly complex; understanding the conceptual model (states, events, guards, pseudo‑states) and the underlying producer‑consumer queue architecture is key to mastering its usage and extending it in real‑world applications.

backendJavastate managementSpringPersistenceevent processingStateMachine
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.