skip to content

How do you configure a Spring Statemachine using StateMachineConfigurer / StateMachineConfigurerAdapter?

level: middleimportance: should knowfreq 40%

answer

  1. @EnableStateMachine vs @EnableStateMachineFactory
  2. extends StateMachineConfigurerAdapter<S,E>
  3. 3 configure(): config / states / transitions
  4. withStates().initial().states().end()
  5. withExternal/withInternal/withLocal .source.target.event

basics

~10 s

Annotate a config class with @EnableStateMachine and extend StateMachineConfigurerAdapter<S,E>. Override the three configure() methods to set global config, declare states (initial/end/normal), and declare transitions (source, target, event, optional guard/action).

solid answer

~30 s

You create a @Configuration class annotated with @EnableStateMachine (single shared machine) or @EnableStateMachineFactory (create instances on demand) and extend StateMachineConfigurerAdapter<S, E>. You override three overloaded configure() methods, each taking a different builder: configure(StateMachineConfigurationBuilder) for global settings (auto-startup, listeners, persistence); configure(StateMachineStateConfigurer) where you call withStates().initial(...).states(EnumSet.allOf(...)).end(...) to declare states; and configure(StateMachineTransitionConfigurer) where you chain withExternal()/withInternal()/withLocal().source(...).target(...).event(...).guard(...).action(...) per transition. Spring builds the StateMachine<S,E> bean from this. There is also a fluent, non-annotation StateMachineBuilder for programmatic construction. Enums are the idiomatic choice for S and E. The state and transition configurers are additive — each with* call is a separate builder segment.

code

java · 33 lines
java
@Configuration
@EnableStateMachineFactory
public class OrderStateMachineConfig
        extends StateMachineConfigurerAdapter<OrderState, OrderEvent> {

    @Override
    public void configure(StateMachineConfigurationBuilder<OrderState, OrderEvent> c)
            throws Exception {
        c.withConfiguration().autoStartup(true);
    }

    @Override
    public void configure(StateMachineStateConfigurer<OrderState, OrderEvent> states)
            throws Exception {
        states.withStates()
              .initial(OrderState.NEW)
              .states(EnumSet.allOf(OrderState.class))
              .end(OrderState.DELIVERED)
              .end(OrderState.CANCELLED);
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<OrderState, OrderEvent> t)
            throws Exception {
        t.withExternal().source(OrderState.NEW).target(OrderState.PAID).event(OrderEvent.PAY)
         .and()
         .withExternal().source(OrderState.PAID).target(OrderState.SHIPPED).event(OrderEvent.SHIP)
         .and()
         .withExternal().source(OrderState.SHIPPED).target(OrderState.DELIVERED).event(OrderEvent.DELIVER)
         .and()
         .withExternal().source(OrderState.NEW).target(OrderState.CANCELLED).event(OrderEvent.CANCEL);
    }
}

go deeper

for a junior

Knows there's a config class and you declare states and transitions there.

for a middle

Can name StateMachineConfigurerAdapter, the two enable annotations, and the three configure overloads and what each declares.

for a senior

Distinguishes external/internal/local transitions in config, factory-vs-single, autoStartup, and uses StateMachineBuilder when appropriate.

for a principal

Chooses config strategy for scale/concurrency, isolates machine lifecycle per aggregate, and treats the config as the enforceable spec plus wiring persistence/listeners.

## The configuration entry points Spring Statemachine offers two ways to build a machine: 1. **Adapter-based Java config (most common).** A `@Configuration` class annotated with either: - `@EnableStateMachine` — exposes **one shared** `StateMachine<S,E>` bean, or - `@EnableStateMachineFactory` — exposes a `StateMachineFactory<S,E>` so you can call `factory.getStateMachine()` / `getStateMachine(String id)` to build **fresh instances** (essential when each user/order needs its own machine, because a single machine is stateful and not safe to share concurrently). The class extends **`StateMachineConfigurerAdapter<S, E>`** and overrides overloaded `configure(...)` methods. 2. **`StateMachineBuilder`** — a fully programmatic fluent builder (`StateMachineBuilder.builder()`) used when you can't or don't want annotation-driven config, e.g. building machines dynamically at runtime. ## The three configure() overloads The adapter has three `configure` methods, distinguished by their builder parameter: ### 1. `configure(StateMachineConfigurationBuilder<S,E> config)` — global config Controls machine-wide behaviour via `config.withConfiguration()`: - `.autoStartup(true)` — start the machine automatically (default is false; otherwise you must call `start()`/`startReactively()`). - `.machineId(...)`, `.listener(myListener)` to register a `StateMachineListener`. Also `config.withPersistence(...)` and `config.withSecurity(...)` hang off here. ### 2. `configure(StateMachineStateConfigurer<S,E> states)` — declare states You call `states.withStates()` and then: - `.initial(S)` — the mandatory initial state (can supply an initial action). - `.state(S)` / `.states(EnumSet.allOf(MyEnum.class))` — the normal states, optionally with entry/exit/do actions: `.state(S, entryAction, exitAction)`. - `.end(S)` — a final state. For **hierarchical** machines you add more `withStates().parent(P)...` blocks; for **regions** you add multiple `withStates()` blocks sharing a parent. ### 3. `configure(StateMachineTransitionConfigurer<S,E> transitions)` — declare transitions Each transition is one chained builder segment: - `transitions.withExternal().source(A).target(B).event(E)` — normal transition that exits A and enters B. - `.withInternal().source(A).event(E).action(a)` — runs an action while staying in A (no exit/entry). - `.withLocal()...` — hierarchical local transition (no exit/re-entry of the shared parent). - optional `.guard(guard)` or `.guardExpression("...")`, and `.action(action)`. You chain one `with*()` per transition; they accumulate. ## What Spring does with it From these three overrides Spring assembles the immutable configuration and produces the `StateMachine<S,E>` bean (or factory). You then inject it, `start()` it (unless autoStartup), and send events. ## Gotchas - **Forgetting an initial state** — configuration fails; every machine needs exactly one initial state per region. - **Using `@EnableStateMachine` where you needed per-instance isolation** — the single bean is stateful; two concurrent flows will corrupt each other. Use `@EnableStateMachineFactory`. - **autoStartup defaults to false** — a freshly injected machine is not started; sending events does nothing until started. - **Mixing the three configurers up** — each `configure` overload receives a *different* builder type; overriding the wrong signature silently does nothing. - Enums aren't required (any type works) but are strongly idiomatic and keep the DSL readable.

  • When would you choose @EnableStateMachineFactory over @EnableStateMachine?
    When you need a separate, isolated machine per business object (per order, per session). @EnableStateMachine gives a single shared, stateful bean that isn't safe for concurrent independent flows; the factory mints fresh instances (optionally keyed by id) each time.
  • What do the three configure() overloads correspond to?
    Global configuration (StateMachineConfigurationBuilder — autoStartup, listeners, persistence), state declarations (StateMachineStateConfigurer — initial/normal/end/hierarchy/regions), and transition declarations (StateMachineTransitionConfigurer — external/internal/local with guards and actions).

saying these in an interview costs you the question

  • Claiming the single @EnableStateMachine bean is safe to share across concurrent flows
  • Forgetting to declare an initial state
  • Assuming the machine auto-starts (autoStartup is false by default)
  • Confusing which configure overload declares states vs transitions

context