Backend Development 26 min read

Implementing Order Workflow with Spring Statemachine and Persistent State Storage

This article explains the fundamentals of finite‑state machines, introduces Spring Statemachine, demonstrates how to model an order lifecycle, configure persistence with in‑memory and Redis stores, and provides complete Java code for enums, configuration, services, controllers, listeners, testing, and exception‑handling improvements.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Implementing Order Workflow with Spring Statemachine and Persistent State Storage

1. What Is a State Machine

A state machine (finite‑state machine) is a mathematical model that describes a set of states and the transitions between them, such as an automatic door with open and closed states.

1.1 What Is a State

Real‑world objects have distinct states; a state machine abstracts these into a diagram that defines how inputs (events) cause state transitions.

1.2 Four Core Concepts

State : the condition of the object (e.g., open , closed ).

Event : the trigger that causes a transition (e.g., pressing the open button).

Action : the operation executed when an event occurs (often a method call).

Transition : the movement from one state to another.

1.3 Finite‑State Machine (FSM)

An FSM consists of a finite set of states, an initial state, inputs, and a transition function that determines the next state based on the current state and input.

2. State Machine Diagram

For an order process, the diagram includes six elements: start, end, current state, target state, action, and condition. Example: WAIT_PAYMENT → (pay) → WAIT_DELIVER → (deliver) → WAIT_RECEIVE → (receive) → FINISH .

3. Spring Statemachine

3.1 Overview

Spring Statemachine provides a framework for using state‑machine concepts within Spring applications, offering simple flat machines, hierarchical structures, regions, triggers, guards, actions, a generator pattern, Zookeeper‑based distributed machines, listeners, and integration with Spring IoC.

3.2 Quick Start

Define a table tb_order to store order data:

CREATE TABLE `tb_order` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `order_code` varchar(128) DEFAULT NULL COMMENT '订单编码',
  `status` smallint DEFAULT NULL COMMENT '订单状态',
  `name` varchar(64) DEFAULT NULL COMMENT '订单名称',
  `price` decimal(12,2) DEFAULT NULL COMMENT '价格',
  `delete_flag` tinyint(2) NOT NULL DEFAULT '0' COMMENT '删除标记,0未删除  1已删除',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '更新时间',
  `create_user_code` varchar(32) DEFAULT NULL COMMENT '创建人',
  `update_user_code` varchar(32) DEFAULT NULL COMMENT '更新人',
  `version` int DEFAULT '0' COMMENT '版本号',
  `remark` varchar(64) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='订单表';

Add the required Maven dependencies:

org.springframework.statemachine
spring-statemachine-redis
1.2.9.RELEASE
org.springframework.statemachine
spring-statemachine-starter
2.0.1.RELEASE

3.3 Defining States and Events

public enum OrderStatus {
    WAIT_PAYMENT(1, "待支付"),
    WAIT_DELIVER(2, "待发货"),
    WAIT_RECEIVE(3, "待收货"),
    FINISH(4, "已完成");
    private Integer key;
    private String desc;
    // getters and utility methods omitted for brevity
}
public enum OrderStatusChangeEvent { PAYED, DELIVERY, RECEIVED; }

3.4 Configuring the State Machine

@Configuration
@EnableStateMachine(name = "orderStateMachine")
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter
{
    @Override
    public void configure(StateMachineStateConfigurer
states) throws Exception {
        states.withStates()
              .initial(OrderStatus.WAIT_PAYMENT)
              .states(EnumSet.allOf(OrderStatus.class));
    }
    @Override
    public void configure(StateMachineTransitionConfigurer
transitions) throws Exception {
        transitions.withExternal().source(OrderStatus.WAIT_PAYMENT).target(OrderStatus.WAIT_DELIVER).event(OrderStatusChangeEvent.PAYED)
                   .and()
                   .withExternal().source(OrderStatus.WAIT_DELIVER).target(OrderStatus.WAIT_RECEIVE).event(OrderStatusChangeEvent.DELIVERY)
                   .and()
                   .withExternal().source(OrderStatus.WAIT_RECEIVE).target(OrderStatus.FINISH).event(OrderStatusChangeEvent.RECEIVED);
    }
}

3.5 Persistence

Memory persistence bean:

@Bean(name = "stateMachineMemPersister")
public static StateMachinePersister getPersister() {
    return new DefaultStateMachinePersister(new StateMachinePersist() {
        private Map map = new HashMap();
        @Override
        public void write(StateMachineContext context, Object contextObj) {
            map.put(contextObj, context);
        }
        @Override
        public StateMachineContext read(Object contextObj) {
            return (StateMachineContext) map.get(contextObj);
        }
    });
}

Redis persistence bean (requires Redis connection factory):

@Bean(name = "stateMachineRedisPersister")
public RedisStateMachinePersister
getRedisPersister() {
    RedisStateMachineContextRepository
repository = new RedisStateMachineContextRepository<>(redisConnectionFactory);
    RepositoryStateMachinePersist
p = new RepositoryStateMachinePersist<>(repository);
    return new RedisStateMachinePersister<>(p);
}

4. Business Layer

Controller endpoints for creating orders and driving the workflow:

@RestController
@RequestMapping("/order")
public class OrderController {
    @Resource private OrderService orderService;
    @RequestMapping("/create")
    public String create(@RequestBody Order order) { orderService.create(order); return "success"; }
    @RequestMapping("/pay")
    public String pay(@RequestParam("id") Long id) { orderService.pay(id); return "success"; }
    @RequestMapping("/deliver")
    public String deliver(@RequestParam("id") Long id) { orderService.deliver(id); return "success"; }
    @RequestMapping("/receive")
    public String receive(@RequestParam("id") Long id) { orderService.receive(id); return "success"; }
}

Service implementation uses the state machine, persists state, and logs actions:

@Service("orderService")
public class OrderServiceImpl extends ServiceImpl
implements OrderService {
    @Resource private StateMachine
orderStateMachine;
    @Resource private StateMachinePersister
stateMachineMemPersister;
    @Resource private OrderMapper orderMapper;
    public Order create(Order order) { order.setStatus(OrderStatus.WAIT_PAYMENT.getKey()); orderMapper.insert(order); return order; }
    public Order pay(Long id) { Order order = orderMapper.selectById(id); if (!sendEvent(OrderStatusChangeEvent.PAYED, order, CommonConstants.payTransition)) { throw new RuntimeException("支付失败, 订单状态异常"); } return order; }
    // deliver and receive methods omitted for brevity
    private synchronized boolean sendEvent(OrderStatusChangeEvent changeEvent, Order order, String key) {
        boolean result = false;
        try {
            orderStateMachine.start();
            stateMachineMemPersister.restore(orderStateMachine, String.valueOf(order.getId()));
            Message message = MessageBuilder.withPayload(changeEvent).setHeader("order", order).build();
            result = orderStateMachine.sendEvent(message);
            if (!result) return false;
            Integer flag = (Integer) orderStateMachine.getExtendedState().getVariables().get(key + order.getId());
            orderStateMachine.getExtendedState().getVariables().remove(key + order.getId());
            if (Objects.equals(1, flag)) {
                stateMachineMemPersister.persist(orderStateMachine, String.valueOf(order.getId()));
            } else {
                return false;
            }
        } catch (Exception e) {
            // log error
        } finally {
            orderStateMachine.stop();
        }
        return result;
    }
}

4.1 Listener with Transactional Transitions

@Component("orderStateListener")
@WithStateMachine(name = "orderStateMachine")
public class OrderStateListenerImpl {
    @Resource private OrderMapper orderMapper;
    @OnTransition(source = "WAIT_PAYMENT", target = "WAIT_DELIVER")
    @Transactional(rollbackFor = Exception.class)
    @LogResult(key = CommonConstants.payTransition)
    public void payTransition(Message
message) {
        Order order = (Order) message.getHeaders().get("order");
        order.setStatus(OrderStatus.WAIT_DELIVER.getKey());
        orderMapper.updateById(order);
        if (Objects.equals(order.getName(), "A")) { throw new RuntimeException("执行业务异常"); }
    }
    // deliverTransition and receiveTransition omitted for brevity
}

5. Testing the Workflow

Typical API calls:

Create order: POST http://localhost:8084/order/create

Pay order: GET http://localhost:8084/order/pay?id=2

Deliver order: GET http://localhost:8084/order/deliver?id=2

Receive order: GET http://localhost:8084/order/receive?id=2

Repeating a transition (e.g., paying twice) results in an error because the state machine prevents illegal state changes.

6. Problems and Solutions

6.1 State Machine Swallows Exceptions

Calling orderStateMachine.sendEvent(message) always returns true , even when a listener throws an exception. The exception is consumed by the state machine, making it impossible for the caller to detect failure.

6.2 Using Extended State to Propagate Results

Listeners store a success flag (1) or failure flag (0) in orderStateMachine.getExtendedState().getVariables() . The sender reads this flag after sendEvent and decides whether to persist the state.

6.3 AOP Wrapper for Listeners

An annotation @LogResult(key = "payTransition") and an aspect intercept the listener method, automatically writing 1 on success or 0 on exception, removing duplicated try/catch code.

6.4 Refactored SendEvent Method

The sendEvent method now accepts a key parameter, reads the flag from the extended state, persists only on success, and returns false when the business logic fails.

7. Conclusion

The article demonstrates a complete, production‑ready implementation of an order workflow using Spring Statemachine, covering state definition, configuration, persistence strategies, transactional listeners, testing, and advanced error‑handling techniques.

JavaAOPworkflowRedisSpringState MachinePersistence
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.

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.