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.
All code compiles and runs with Java 25. No external dependencies required.
Pattern Structure

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
The state interface declares everything a concrete state needs to implement — entering the state, handling the transition trigger, and reporting its own color:
TrafficLightState.java
package state;
/** State interface — each concrete state handles events differently */
public interface TrafficLightState {
void onEnter(TrafficLight light);
void next(TrafficLight light);
String getColor();
}
The context holds only a reference to the current state and delegates every call to it — there is no if-else anywhere in this class:
TrafficLight.java
package state;
/** Context — holds the current state and delegates behaviour to it */
public class TrafficLight {
private TrafficLightState state;
public TrafficLight() {
setState(new RedState());
}
public void setState(TrafficLightState newState) {
this.state = newState;
state.onEnter(this);
}
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.
RedState.java
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());
}
@Override public String getColor() { return "RED"; }
}
GreenState transitions to Yellow:
GreenState.java
package state;
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"; }
}
And YellowState closes the cycle back to Red:
YellowState.java
package state;
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"; }
}
Main creates the light (which starts Red), cycles through six transitions, then prints a short summary of why this beats the if-else version:
Main.java
package state;
/**
* State Design Pattern — Runnable Demo
* Run: javac state/*.java -d out/state && java -cp out/state state.Main
* Article: https://ankurm.com/state-design-pattern-java/
*/
public class Main {
public static void main(String[] args) {
System.out.println("=== State Design Pattern Demo ===\n");
TrafficLight light = new TrafficLight();
System.out.println("Starting state: " + light.getColor());
System.out.println("\n-- Cycling through states --");
for (int i = 0; i < 6; i++) {
light.next();
}
System.out.println("\n-- Without State pattern: a single class with if/else --");
System.out.println(" Every new state adds to every method's if-else chain.");
System.out.println(" With State: add one new class, touch nothing else.");
System.out.println("\n=== Demo complete ===");
}
}
Console Output
[RED] Stop. Pedestrians cross.
Starting state: 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.
— Without State pattern: a single class with if/else —
Every new state adds to every method’s if-else chain.
With State: add one new class, touch nothing else.
=== 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
- Strategy Design Pattern in Java — same structure, different intent: Strategy swaps algorithms at caller’s request; State manages self-driven transitions
- Command Design Pattern in Java — State transitions can be modelled as Commands for undo-able state changes
Further Reading
- State — Refactoring.Guru
- Design Patterns: Elements of Reusable Object-Oriented Software — Gamma et al., Chapter 5
No Comments yet!