State Machine
1. What is a state machine and how it works
A state machine is an abstraction often used in simulation models. It consists of states and transitions between them. Transitions define the directions of possible state changes. A transition can connect a state to a different state or to itself. When a transition executes, the current state of the state machine changes from source state of the transition to its target state.
Examples of use of state machines include, an employee can be on shift or off shift, a truck can be idle, out of order or performing a transportation task, etc.
It is natural to model such cases with StateMachine
class because it:
-
clearly defines which transitions are possible, and which are not,
-
provides simple API for handling state entries and exits,
-
provides out-of-the-box API for statistics about what time was spent in each state,
-
has built-in tools for visualization.
A visual representation of a simple state machine can look like this:

The state machine from the figure above can be created with the following code:
Engine engine = new Engine();
// Create a state machine with 2 states of type String - "A" and "B", let "A" be the initial state
StateMachine<String> stateMachine = new StateMachine<>(new String[] { "A", "B" }, "A", engine);
// Add all default transitions between all different states
stateMachine.addAllTransitions();
A state machine is always in exactly one state.
After creation, it is in the initial state specified in the constructor.
Current state can be retrieved by currentState()
method.
State machine is a subset of a more general concept called finite-state machine.
2. Transitions and messages
Transitions are executed when state machine receives messages.
Messages are sent to the state machine by calling receiveMessage
method:
stateMachine.receiveMessage("B");
Transitions can be of 2 types:
-
A default transition is a transition that is triggered by message of the target state. In the example above, the default transition from "A" to "B" would be triggered by the message "B". A default transition is added just be specifying its source and target states:
stateMachine.addTransition("A", "B");
-
A transition with predicate is a transition that calculates the boolean expression over the message sent to this state machine and executes if the result of the calculation is
true
. A transition with predicate is added like this:
stateMachine.addTransition("A", "B", m -> "MSG".equals(m));
3. Actions
3.1. Actions for specific states
Actions can be executed when some specific states are entered and exited.
The actions can be added by methods of addEnterAction
and addExitAction
:
stateMachine.addEnterAction("B", x -> System.out.println("Entered B"));
stateMachine.addExitAction("A", x -> System.out.println("Exited A"));
State machine is in one of its states even inside the code of actions. All enter actions are executed after entering the corresponding state. All exit actions are executed before exiting the corresponding state.
There can be several actions associated with entry and exit of every state. If needed, an action can be inserted before all other previously added actions:
stateMachine.addEnterAction("B", x -> System.out.println("Entry action #1"));
stateMachine.addEnterAction("B", HandlerOrder.BEFORE_OTHER_HANDLERS, x -> System.out.println("Entry action #2. Executed before action #1"));
3.2. Actions for all states
Sometimes there is a need to handle entry events of all states.
Typical examples of such cases include collecting statistics or logging state changes.
There is a specific prototype of addEnterAction
and addExitAction
methods that take BiConsumer
as its only argument.
The first argument passed to this BiConsumer
is the state that is entered or exited.
The second argument is the message with which the corresponding state was entered or is about to exit.
The example of adding a handler for entry of any state is shown below:
stateMachine.addEnterAction((state, message) -> System.out.println("State " + state + " entered with message " + message));
4. Using state machines with enums
The common practice is to use Java enums as type of states of a state machine. Here is the simple traffic light class that demonstrates such use:
public class TrafficLight {
public enum Light { GREEN, AMBER, RED }
private StateMachine<Light> stateMachine;
public TrafficLight(Engine engine) {
stateMachine = new StateMachine<>(Light.values(), Light.GREEN, engine);
}
}
5. Fluent API to construct state machines
Many methods of state machine class return the reference to itself to support the fluent construction API like this:
StateMachine<Light> stateMachine = new StateMachine<>(Light.values(), GREEN, engine)
.addTransition(GREEN, AMBER)
.addTransition(AMBER, RED)
.addTransition(RED, AMBER)
.addTransition(AMBER, GREEN)
.addEnterAction(RED, msg -> System.out.println("Red light!"));
6. Nested message calls
It is possible to send messages to state machine from inside code of handling its actions. This is the simplest example of it:
Engine engine = new Engine();
StateMachine<String> stateMachine = new StateMachine<>(new String[] {"A", "B", "C"}, "A", engine);
// After "B" entered, send "C" message to the state machine
stateMachine.addEnterAction("B", msg -> stateMachine.receiveMessage("C"));
stateMachine.addAllTransitions();
// Initiate state changes by sending "B" message
stateMachine.receiveMessage("B");
// Check that finally the state machine is in "C" state
assert(stateMachine.currentState().equals("C"));
Nested message calls allow developers to simulate complex logic when multiple transitions occur during the same simulation time.