Mastering Order Workflow with Spring Statemachine: A Step‑by‑Step Guide

This tutorial shows how to replace bulky if‑else or switch logic with Spring Statemachine in a Spring Boot application, covering dependency setup, state and event enums, machine configuration, persistence, service implementation, controller endpoints, and advanced conditional transitions for loan processing.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Mastering Order Workflow with Spring Statemachine: A Step‑by‑Step Guide

Background

When handling loan order status transitions, using plain if‑else or switch statements quickly becomes hard to maintain. A state machine offers a structured, maintainable way to manage these transitions.

1. Add Spring Statemachine Dependency

<dependency>
  <groupId>org.springframework.statemachine</groupId>
  <artifactId>spring-statemachine-core</artifactId>
  <version>4.0.0</version>
</dependency>

2. Define Order Status Enum

public enum OrderStatusEnum {
  // 待审核
  APPROVE_PENDING,
  // 审核中
  APPROVE_ING,
  // 审核失败
  APPROVE_FAILED,
  // 审核成功
  APPROVE_SUCCESS
}

3. Define Order Event Enum

public enum OrderEvent {
  // 开始审核
  APPROVE_START,
  // 审核通过
  APPROVE_SUCCESS,
  // 审核失败
  APPROVE_FAILED
}

4. Configure the State Machine

@Configuration
@EnableStateMachine(name = "OrderStateMachine")
@Slf4j
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStatusEnum, OrderEvent> {

  @Resource
  private OrderMapper orderMapper;

  /** Configure states */
  @Override
  public void configure(StateMachineStateConfigurer<OrderStatusEnum, OrderEvent> states) throws Exception {
    states.withStates()
          .initial(OrderStatusEnum.APPROVE_PENDING) // 初始状态
          .states(EnumSet.allOf(OrderStatusEnum.class));
  }

  /** Configure transitions */
  @Override
  public void configure(StateMachineTransitionConfigurer<OrderStatusEnum, OrderEvent> transitions) throws Exception {
    transitions
      // 待审核 -> 审核中
      .withExternal().source(OrderStatusEnum.APPROVE_PENDING).target(OrderStatusEnum.APPROVE_ING).event(OrderEvent.APPROVE_START).and()
      // 审核中 -> 审核失败
      .withExternal().source(OrderStatusEnum.APPROVE_ING).target(OrderStatusEnum.APPROVE_FAILED).event(OrderEvent.APPROVE_FAILED).and()
      // 审核中 -> 审核成功
      .withExternal().source(OrderStatusEnum.APPROVE_ING).target(OrderStatusEnum.APPROVE_SUCCESS).event(OrderEvent.APPROVE_SUCCESS);
  }

  /** Persistence bean */
  @Bean
  public DefaultStateMachinePersister<OrderStatusEnum, OrderEvent, BizOrder> persister() {
    return new DefaultStateMachinePersister<>(new StateMachinePersist<OrderStatusEnum, OrderEvent, BizOrder>() {
      @Override
      public void write(StateMachineContext<OrderStatusEnum, OrderEvent> context, BizOrder order) throws Exception {
        OrderStatusEnum orderStatus = context.getState();
        log.info("订单状态持久化,订单ID:{},目标状态:{}", order.getId(), orderStatus);
        orderMapper.updateOrderStatus(order.getId(), orderStatus);
      }
      @Override
      public StateMachineContext<OrderStatusEnum, OrderEvent> read(BizOrder order) throws Exception {
        log.info("恢复订单状态机状态");
        return new DefaultStateMachineContext<>(order.getStatus(), null, null, null);
      }
    });
  }
}

5. Service Interface for State Changes

public interface BizOrderStatusService {
  /**
   * 通用状态变更处理器
   */
  void eventHandler(Long orderId, OrderEvent event);
}

6. Service Implementation

@Service
public class BizOrderStatusServiceImpl implements BizOrderStatusService {

  @Resource
  private OrderMapper orderMapper;

  @Resource
  private StateMachine<OrderStatusEnum, OrderEvent> orderStateMachine;

  @Resource
  private StateMachinePersister<OrderStatusEnum, OrderEvent, BizOrder> persister;

  @Override
  public void eventHandler(Long orderId, OrderEvent event) {
    BizOrder order = orderMapper.getOrderById(orderId);
    Assert.notNull(order, "订单不存在");
    StateMachineParam param = new StateMachineParam();
    param.setBizOrder(order);
    Message<OrderEvent> message = MessageBuilder.withPayload(event).build();
    if (!sendEvent(message, param)) {
      throw new ApplicationBizException("订单状态流转异常");
    }
  }

  /**
   * 发送事件(示例中使用 synchronized,仅示意)
   */
  private synchronized boolean sendEvent(Message<OrderEvent> message, StateMachineParam param) {
    boolean result = false;
    try {
      orderStateMachine.start();
      persister.restore(orderStateMachine, param.getBizOrder());
      orderStateMachine.getExtendedState().getVariables().put("param", param);
      result = orderStateMachine.sendEvent(message);
      persister.persist(orderStateMachine, param.getBizOrder());
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      orderStateMachine.stop();
    }
    return result;
  }
}

7. Controller Endpoints

@RestController
@RequiredArgsConstructor
public class ApproveController {

  private final OrderStatusService orderStatusService;

  /** 将订单状态改为审核中 */
  @PostMapping("/start")
  public void start(Long orderId) {
    orderStatusService.eventHandler(orderId, OrderEvent.APPROVE_START);
  }

  /** 将订单状态改为审核成功 */
  @PostMapping("/approveSuccess")
  public void approveSuccess(Long orderId) {
    orderStatusService.eventHandler(orderId, OrderEvent.APPROVE_SUCCESS);
  }

  /** 将订单状态改为审核失败 */
  @PostMapping("/approveFailed")
  public void approveFailed(Long orderId) {
    orderStatusService.eventHandler(orderId, OrderEvent.APPROVE_FAILED);
  }
}

8. Extending the Model for Loan Scenarios

To handle loan outcomes, add two new status values and a new event:

public enum OrderStatusEnum {
  // ... existing statuses ...
  LOAN_SUCCESS,
  PARTIALLY_LOAN_SUCCESS;
}
public enum OrderEvent {
  // ... existing events ...
  LOAN;
}

9. Conditional Transitions with Guards

Configure the state machine so that the LOAN event can lead to either LOAN_SUCCESS or PARTIALLY_LOAN_SUCCESS based on the order’s amounts.

@Override
public void configure(StateMachineTransitionConfigurer<OrderStatusEnum, OrderEvent> transitions) throws Exception {
  transitions
    // existing transitions ...
    .withExternal().source(OrderStatusEnum.APPROVE_SUCCESS).target(OrderStatusEnum.LOAN_SUCCESS)
        .event(OrderEvent.LOAN).guard(guardForLoanSuccessByLoan()).and()
    .withExternal().source(OrderStatusEnum.APPROVE_SUCCESS).target(OrderStatusEnum.PARTIALLY_LOAN_SUCCESS)
        .event(OrderEvent.LOAN).guard(guardForPartiallyLoanSuccessByLoan());
}

@Bean
public Guard<OrderStatusEnum, OrderEvent> guardForLoanSuccessByLoan() {
  return context -> {
    StateMachineParam param = (StateMachineParam) context.getExtendedState().getVariables().get("param");
    BizOrder order = param.getBizOrder();
    return order.getApplyAmount().compareTo(order.getLoanAmount()) == 0;
  };
}

@Bean
public Guard<OrderStatusEnum, OrderEvent> guardForPartiallyLoanSuccessByLoan() {
  return context -> {
    StateMachineParam param = (StateMachineParam) context.getExtendedState().getVariables().get("param");
    BizOrder order = param.getBizOrder();
    return order.getApplyAmount().compareTo(order.getLoanAmount()) < 0;
  };
}

With these configurations, the state machine can route the same LOAN event to different target states depending on business conditions, enabling flexible workflow handling.

backendJavastate machineSpring BootStatemachineOrder WorkflowConditional Transition
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

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.