alibaba/COLA · error · StateMachineException

The state machine with id [${machineId}] is already built, n

Error message

The state machine with id [${machineId}] is already built, no need to build again

What it means

StateMachineFactory keeps a static registry of state machines keyed by machineId. register() throws this StateMachineException when a machine with the same id is already present in the map, to enforce one machine per id. It fires when the same machineId is built and registered twice.

Source

Thrown at cola-components/cola-component-statemachine/src/main/java/com/alibaba/cola/statemachine/StateMachineFactory.java:20

import com.alibaba.cola.statemachine.impl.StateMachineException;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * StateMachineFactory
 *
 * @author Frank Zhang
 * @date 2020-02-08 10:21 PM
 */
public class StateMachineFactory {
    static Map<String /* machineId */, StateMachine> stateMachineMap = new ConcurrentHashMap<>();

    public static <S, E, C> void register(StateMachine<S, E, C> stateMachine){
        String machineId = stateMachine.getMachineId();
        if(stateMachineMap.get(machineId) != null){
            throw new StateMachineException("The state machine with id ["+machineId+"] is already built, no need to build again");
        }
        stateMachineMap.put(stateMachine.getMachineId(), stateMachine);
    }

    public static <S, E, C> StateMachine<S, E, C> get(String machineId){
        StateMachine stateMachine = stateMachineMap.get(machineId);
        if(stateMachine == null){
            throw new StateMachineException("There is no stateMachine instance for "+machineId+", please build it first");
        }
        return stateMachine;
    }
}

View on GitHub (pinned to 352e1a8675)

Solutions

  1. Use StateMachineFactory.get(machineId) first and only build/register when it returns null
  2. Use unique machineIds (e.g. append a timestamp/UUID) if multiple instances are intentional
  3. Guard initialization with a singleton/once flag so build code runs once per JVM
  4. In tests, build in a static initializer or check the factory cache before rebuilding

Example fix

// before
StateMachine<...> sm = builder.build("orderSM"); // second call throws

// after
StateMachine<...> sm = StateMachineFactory.get("orderSM");
if (sm == null) {
    sm = builder.build("orderSM");
}
Defensive patterns

Strategy: validation

Validate before calling

if (StateMachineFactory.get(machineId) != null) {
    throw new IllegalStateException("machineId already registered: " + machineId);
}

Try / catch

try {
    StateMachineFactory.register(sm);
} catch (StateMachineException e) {
    if (e.getMessage().contains("already built")) {
        sm = StateMachineFactory.get(machineId); // reuse existing
    } else {
        throw e;
    }
}

Prevention

When it happens

Trigger: Calling StateMachineBuilder.builder(...) ... build(machineId) (or StateMachineFactory.register) twice with the same machineId, e.g. re-running initialization code on app restart within the same JVM or duplicate Spring bean creation.

Common situations: Hot-reload or repeated initialization of state machines in tests (multiple @BeforeAll building the same id); duplicate bean definitions; classloader re-loading in app servers.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


AI-assisted analysis of alibaba/COLA@352e1a8675 (2026-09-08). Data as JSON: /api/errors/8a17f0863062bc9e. Report an issue: GitHub.