Applying Finite State Machines with Spring Statemachine and Squirrel-foundation in Java

This article explains the concept of finite state machines, outlines scenarios where they are beneficial, compares Spring Statemachine and Squirrel-foundation implementations, and provides detailed configuration, guard/action code, persistence, testing, and practical usage tips for managing complex business workflows in Java backend services.

HomeTech
HomeTech
HomeTech
Applying Finite State Machines with Spring Statemachine and Squirrel-foundation in Java

Preface

In everyday development, many business requirements involve status management such as activity approval, dealer promotions, and tasks. While simple status handling can be done with if‑else statements, the code quickly becomes scattered across services, increasing release risk and regression testing effort.

1. What Is a State Machine?

A finite‑state machine (FSM) is a mathematical model that represents a limited set of states, transitions, and actions. It helps manage object lifecycles by extracting state‑related logic from scattered if‑else blocks. An FSM consists of four elements: current state, condition (event), action, and next state.

Current state – the present state.

Condition (event) – triggers a transition when satisfied.

Action – executed when the condition is met; may be optional.

Next state – the state after transition.

Common action types include entry, exit, input, and transition actions.

2. When to Use a State Machine?

Use a state machine when you need to model part of an application as distinct states, split complex logic into manageable tasks, handle concurrency issues, or replace extensive boolean/enum flag checks with a clear, event‑driven flow.

3. Why Use It? Benefits

Improves code maintainability and reduces complex if‑else logic.

Decouples business logic from state flow, enabling single‑transaction persistence.

Clarifies complex state transitions, reducing “glue” code.

4. Practice

Several Java state‑machine frameworks exist; the article focuses on Spring‑Statemachine (star 1.3K) and Squirrel‑foundation (star 1.9K). Spring‑Statemachine is feature‑rich but heavier, while Squirrel‑foundation is lighter.

pom.xml Dependencies

<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-starter</artifactId>
    <version>2.2.0.RELEASE</version>
</dependency>
<!-- spring‑statemachine context serialization -->
<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-kryo</artifactId>
    <version>2.2.0.RELEASE</version>
</dependency>
<!-- squirrel‑foundation -->
<dependency>
    <groupId>org.squirrelframework</groupId>
    <artifactId>squirrel-foundation</artifactId>
    <version>0.3.10</version>
</dependency>

State & Event Definitions

public enum State {
    INIT("初始化"),
    DRAFT("草稿"),
    WAIT_VERIFY("待审核"),
    PASSED("审核通过"),
    REJECTED("已驳回"),
    WAIT_ONLIE("待上线"),
    ONLINED("已上线"),
    OFFLINING("下线中"),
    OFFLINED("已下线"),
    FINISHED("已结束");
    private final String desc;
}

public enum Event {
    SAVE("保存草稿"),
    SUBMIT("提交审核"),
    PASS("审核通过"),
    REJECT("提交驳回"),
    ONLINE("上线"),
    OFFLINE("下线"),
    FINISH("结束");
    private final String desc;
}

State Transition Configuration (Spring‑Statemachine)

@Configuration
@EnableStateMachineFactory
public class ActivitySpringStateMachineAutoConfiguration extends StateMachineConfigurerAdapter<State, Event> {
    @Autowired private ApplicationContext applicationContext;
    @Autowired private StateMachineRuntimePersister<State, Event, String> activityStateMachinePersister;
    @Bean public StateMachineService<State, Event> activityStateMachineService(StateMachineFactory<State, Event> factory) {
        return new DefaultStateMachineService<>(factory, activityStateMachinePersister);
    }
    @Override public void configure(StateMachineConfigurationConfigurer<State, Event> config) throws Exception {
        config.withPersistence().runtimePersister(activityStateMachinePersister)
              .and().withConfiguration().stateDoActionPolicy(StateDoActionPolicy.TIMEOUT_CANCEL)
              .stateDoActionPolicyTimeout(300, TimeUnit.SECONDS).autoStartup(false);
    }
    @Override public void configure(StateMachineStateConfigurer<State, Event> states) throws Exception {
        states.withStates().initial(State.INIT).choice(State.OFFLINING)
              .states(EnumSet.allOf(State.class));
    }
    @Override public void configure(StateMachineTransitionConfigurer<State, Event> transitions) throws Exception {
        transitions.withExternal().source(State.INIT).target(State.DRAFT).event(Event.SAVE)
            .and().withExternal().source(State.DRAFT).target(State.WAIT_VERIFY).event(Event.SUBMIT)
            .guard(applicationContext.getBean(SubmitCondition.class));
        // ... other transitions omitted for brevity
    }
}

Guard and Action Examples

@Component
public class SaveGuard implements Guard<State, Event> {
    @Override public boolean evaluate(StateContext<State, Event> ctx) {
        log.info("[execute save guard]");
        return true;
    }
}

@Component
public class SaveAction implements Action<State, Event> {
    @Override public void execute(StateContext<State, Event> ctx) {
        try { log.info("[execute saveAction]"); }
        catch (Exception e) { ctx.getExtendedState().getVariables().put("ERROR", e.getMessage()); }
    }
}

Persistence Configuration

@Component
public class ActivityStateMachinePersister extends AbstractStateMachineRuntimePersister<State, Event, String> {
    @Autowired private ActivityStateService activityStateService;
    @Override public void write(StateMachineContext<State, Event> ctx, String id) {
        Activity state = new Activity();
        state.setMachineId(id);
        state.setState(ctx.getState());
        activityStateService.save(state);
    }
    @Override public StateMachineContext<State, Event> read(String id) {
        return deserialize(activityStateService.getContextById(id));
    }
}

Service Invocation and Listener

@Service
public class StateTransitService {
    @Autowired private StateMachineService<State, Event> stateMachineService;
    @Transactional
    public void transimit(String machineId, Message<Event> message) {
        StateMachine<State, Event> sm = stateMachineService.acquireStateMachine(machineId);
        sm.addStateListener(new DefaultStateMachineListener<>(sm));
        sm.sendEvent(message);
        if (sm.hasStateMachineError()) {
            String err = sm.getExtendedState().get("message", String.class);
            stateMachineService.releaseStateMachine(machineId);
            throw new ResponseException(err);
        }
    }
}

@AllArgsConstructor
public class DefaultStateMachineListener<S, E> extends StateMachineListenerAdapter<S, E> {
    private final StateMachine<S, E> stateMachine;
    @Override public void eventNotAccepted(Message<E> event) {
        stateMachine.getExtendedState().getVariables().put("message", "当前状态不满足执行条件");
        stateMachine.setStateMachineError(new ResponseException(500, "Event not accepted"));
    }
    @Override public void transitionEnded(Transition<S, E> t) {
        log.info("source {} to {}", t.getSource().getId(), t.getTarget().getId());
    }
}

Integration Tests

@SpringBootTest @RunWith(SpringRunner.class)
public class StateMachineITest {
    @Autowired private StateTransitService transmitService;
    @Autowired private ActivityStateService activityStateService;
    @Test public void test() {
        String id = "test";
        transmitService.transimit(id, MessageBuilder.withPayload(Event.SAVE).build());
        transmitService.transimit(id, MessageBuilder.withPayload(Event.SUBMIT).build());
        transmitService.transimit(id, MessageBuilder.withPayload(Event.PASS).build());
        transmitService.transimit(id, MessageBuilder.withPayload(Event.ONLINE).build());
        transmitService.transimit(id, MessageBuilder.withPayload(Event.ONLINE).build());
        transmitService.transimit(id, MessageBuilder.withPayload(Event.OFFLINE).build());
        assert activityStateService.getStateById(id).equals(State.FINISHED);
    }
}

Key Takeaways

During development, the state machine simplified complex branching, reduced duplicated if‑else code, and made it easy to add new features such as re‑online after offline. The article also notes the importance of distributed locks, proper error handling, and choosing between Spring‑Statemachine and Squirrel‑foundation based on project complexity.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

workflowSpringstate machinesquirrel-foundation
HomeTech
Written by

HomeTech

HomeTech tech sharing

0 followers
Reader feedback

How this landed with the community

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.