State Design Pattern in Java: Complete Guide with Examples

A vending machine behaves differently depending on whether it’s idle, has money inserted, is dispensing an item, or is out of stock. Without the State pattern, you end up with a single class whose methods contain if-else chains that check the current state variable. When a new state is added, you hunt through every method to add the new branch. The State pattern moves each state’s behaviour into its own class, so adding a new state means adding one class and touching nothing else.

The pattern makes an implicit state machine explicit. Instead of a String currentState field and branching logic spread across every method, you have a reference to a State object whose methods implement exactly the right behaviour for that state. The context just delegates to whatever state object it currently holds.

Pattern Structure

State design pattern structure (via refactoring.guru)
Context delegates all state-specific behaviour to the current State object. States trigger transitions by calling context.setState(). Diagram: refactoring.guru

Without State: The if-else Machine

// Without State — fragile, hard to extend
public class TrafficLight {
    private String state = "RED";
    public void next() {
        if ("RED".equals(state))         { state = "GREEN";  System.out.println("GREEN: Go"); }
        else if ("GREEN".equals(state))  { state = "YELLOW"; System.out.println("YELLOW: Slow"); }
        else if ("YELLOW".equals(state)) { state = "RED";    System.out.println("RED: Stop"); }
        // Adding a FLASHING_YELLOW state means modifying next() AND every other method
    }

With State: Traffic Light

package state;
/** State interface — each concrete state handles events its own way */
public interface TrafficLightState {
    void onEnter(TrafficLight light);  // called when entering this state
    void next(TrafficLight light);     // transitions to the next state
    String getColor();
}
package state;
/** Context — holds the current state and delegates to it */
public class TrafficLight {
    private TrafficLightState state;
    public TrafficLight() {
        setState(new RedState());  // initial state
    }
    public void setState(TrafficLightState newState) {
        this.state = newState;
        state.onEnter(this);
    }
    // Delegate to the current state — no if-else anywhere
    public void next()         { state.next(this); }
    public String getColor()   { return state.getColor(); }
}

Each state class knows only its own entry behaviour and its own transition. RedState doesn’t know what comes after Green; it only knows it transitions to Green. GreenState knows it transitions to Yellow. Adding a FlashingYellowState is one new class with zero changes to existing code.

package state;
public class RedState implements TrafficLightState {
    @Override public void onEnter(TrafficLight light) {
        System.out.println("  [RED]    Stop. Pedestrians cross.");
    }
    @Override public void next(TrafficLight light) {
        light.setState(new GreenState());  // trigger the transition
    }
    @Override public String getColor() { return "RED"; }
}
public class GreenState implements TrafficLightState {
    @Override public void onEnter(TrafficLight light) {
        System.out.println("  [GREEN]  Go. Traffic flows.");
    }
    @Override public void next(TrafficLight light) {
        light.setState(new YellowState());
    }
    @Override public String getColor() { return "GREEN"; }
}
public class YellowState implements TrafficLightState {
    @Override public void onEnter(TrafficLight light) {
        System.out.println("  [YELLOW] Slow down. Prepare to stop.");
    }
    @Override public void next(TrafficLight light) {
        light.setState(new RedState());
    }
    @Override public String getColor() { return "YELLOW"; }
}
public class Main {
    public static void main(String[] args) {
        TrafficLight light = new TrafficLight();
        System.out.println("Starting: " + light.getColor());
        for (int i = 0; i < 6; i++) {
            light.next();
        }
    }
}

Console Output

=== State Design Pattern Demo ===

  [RED]    Stop. Pedestrians cross.
Starting: RED

— Cycling through states —
  [GREEN]  Go. Traffic flows.
  [YELLOW] Slow down. Prepare to stop.
  [RED]    Stop. Pedestrians cross.
  [GREEN]  Go. Traffic flows.
  [YELLOW] Slow down. Prepare to stop.
  [RED]    Stop. Pedestrians cross.

=== Demo complete ===

Who Controls Transitions: State or Context?

There are two design choices. In our example, the state controls transitions: RedState.next() calls light.setState(new GreenState()). This is flexible — different states can transition to different targets based on internal logic.

Alternatively, the context controls transitions: the context’s next() method checks what state it’s in and decides the next state. This centralises transition logic but reintroduces some if-else. Use state-controlled transitions when each state has its own transition logic; use context-controlled transitions when transitions depend on external data (input, conditions) the context holds but states don’t.

💡 State vs Strategy: They look identical structurally — both have a context holding a reference to an interchangeable object. The difference is intent and lifecycle. Strategy objects are stateless algorithms swapped explicitly by the client. State objects are stateful actors that transition themselves or trigger transitions in the context — the client calls next() and the machine decides what happens. If the object decides its own next state, it’s State. If the caller decides which behaviour to use, it’s Strategy.

State in the JDK and Frameworks

Java’s Thread is a state machine: NEW → RUNNABLE → BLOCKED/WAITING/TIMED_WAITING → TERMINATED. Each state determines what start(), sleep(), and interrupt() do. The JVM manages these transitions internally using the State pattern.

Spring’s @Stateful session beans and workflow engines like Activiti and Camunda are explicit state machine implementations built on the State pattern, where workflow steps and their transitions are defined declaratively.

✅ Enums for Simple State Machines: Not every state machine needs the full State pattern. If the states have no per-state behaviour (only data) and the transitions are simple, a Java enum with a transitions map is clean and easy to read. Use the State pattern when: different states have fundamentally different implementations of the same operations, states need to hold their own data, or the machine is complex enough that a class-per-state pays for its overhead in clarity and testability.

When to Use State

Reach for State when: an object’s behaviour changes based on an internal state and there are many states with distinct behaviour per state. You have large conditional statements in multiple methods that switch on the same state variable. State transitions involve complex logic, guards, or entry/exit actions that you want to encapsulate per state.

Avoid it when: there are only 2-3 states with minimal behaviour difference — a simple boolean flag or enum is clearer. The number of state classes becomes very large and navigation becomes hard — consider a table-driven state machine library. State objects need access to lots of context data, creating tight coupling in the other direction.

Runnable Code on GitHub

Full source at ankurm.com/git.app/asmhatre/design-patterns under 03-behavioral/state/.

javac 03-behavioral/state/*.java -d out/state
java -cp out/state state.Main

See Also

Further Reading

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.