State Machine Pattern

A Finite State Machine (FSM) models a system as a set of discrete states and the transitions between them. In software engineering, this pattern prevents "illegal states" (e.g., a cancelled order being shipped) by making transitions explicit and type-safe.

Core Components

  1. State: A distinct condition of the system (e.g., PENDING, ACTIVE).
  2. Event: An input that triggers a potential transition.
  3. Transition: The move from state A to state B.
  4. Guard: A boolean condition that must be met for a transition to occur.
  5. Action: A side effect executed during transition (e.g., sending an email).

Implementation Strategy: Sealed Interfaces (Java 21+)

Modern Java allows for extremely robust FSMs using sealed interfaces and records. This ensures that the set of states is fixed and exhaustive, allowing the compiler to check for missing transition logic.

Concrete Example: Connection Lifecycle

public sealed interface ConnectionState 
    permits Disconnected, Connecting, Connected, Error {}

public record Disconnected() implements ConnectionState {}
public record Connecting(Instant attemptStarted) implements ConnectionState {}
public record Connected(String sessionId, Instant connectedAt) implements ConnectionState {}
public record Error(String message, int retryCount) implements ConnectionState {}

public class Connection {
    private ConnectionState state = new Disconnected();

    public void connect() {
        state = switch (state) {
            case Disconnected d -> new Connecting(Instant.now());
            case Error e when e.retryCount() < 3 -> new Connecting(Instant.now());
            case Connecting c -> c; // Idempotent
            case Connected c -> throw new IllegalStateException("Already connected");
            default -> throw new IllegalStateException("Cannot connect from " + state);
        };
    }

    public void onEstablished(String sid) {
        if (state instanceof Connecting) {
            this.state = new Connected(sid, Instant.now());
        } else {
            throw new IllegalStateException("Received SID while in state: " + state);
        }
    }
}

Transition Tables

For complex machines with many states, a transition table (often implemented as a Map<State, Map<Event, State>>) is cleaner than switch blocks.

From StateEventTo StateAction/Guard
INITIALSTARTRUNNINGinitSystem()
RUNNINGPAUSEPAUSEDpersistContext()
PAUSEDRESUMERUNNINGloadContext()
RUNNINGERRORFAILEDlogError()

Best Practices

Anti-Patterns