State Design Pattern in Java
Change an object’s behavior when its internal state changes.
What is State?
The State Design Pattern is a behavioral pattern from the Gang of Four (GoF). It allows an object to change its behavior when its internal state changes, so the object appears to change its class. Instead of scattering state rules across large if-else or switch statements, the behavior for each state is encapsulated in a separate class. The State interface defines common operations, Concrete State classes implement state-specific behavior, and the Context stores the current state and delegates requests to it. Use this pattern when behavior depends on the current state, conditional logic is becoming difficult to maintain, or new states need to be added easily. A traffic light demonstrates the idea clearly: Red means stop, Green means go, and Yellow means prepare to stop. The same approach works for e-commerce orders, where an order can move from Placed to Shipped to Delivered or Cancelled. State-specific rules remain inside their state classes, making transitions easier to understand, test, and extend. Advantages include better maintainability, less conditional logic, support for the Open/Closed Principle, and clearer state transitions. The main trade-off is that every state requires a class, which can increase the number of files and create a small amount of object overhead.
Beginner-friendly Java example
Focus on the roles in the example first. The pattern becomes easier when you can identify the sender, receiver, context, state, or strategy involved.
// Example 1: Traffic light system
// Step 1: Define the State interface
interface TrafficLightState {
void handleRequest(TrafficLightContext context);
}
// Step 2: Implement concrete states
class RedLightState implements TrafficLightState {
@Override
public void handleRequest(TrafficLightContext context) {
System.out.println("Red Light: Cars must stop.");
context.setState(new GreenLightState());
}
}
class GreenLightState implements TrafficLightState {
@Override
public void handleRequest(TrafficLightContext context) {
System.out.println("Green Light: Cars can go.");
context.setState(new YellowLightState());
}
}
class YellowLightState implements TrafficLightState {
@Override
public void handleRequest(TrafficLightContext context) {
System.out.println("Yellow Light: Cars should prepare to stop.");
context.setState(new RedLightState());
}
}
// Step 3: Create the Context
class TrafficLightContext {
private TrafficLightState currentState;
public TrafficLightContext() {
currentState = new RedLightState();
}
public void setState(TrafficLightState state) {
this.currentState = state;
}
public void changeLight() {
currentState.handleRequest(this);
}
}
// Step 4: Client code
public class StatePatternDemo {
public static void main(String[] args) {
TrafficLightContext trafficLight = new TrafficLightContext();
for (int i = 0; i < 6; i++) {
trafficLight.changeLight();
System.out.println();
}
}
}
// Example 2: E-commerce order states
interface OrderState {
void next(OrderContext context);
void cancel(OrderContext context);
}
class OrderPlacedState implements OrderState {
public void next(OrderContext context) {
System.out.println("Order has been placed. Moving to Shipped state.");
context.setState(new OrderShippedState());
}
public void cancel(OrderContext context) {
System.out.println("Order has been cancelled.");
context.setState(new OrderCancelledState());
}
}
class OrderShippedState implements OrderState {
public void next(OrderContext context) {
System.out.println("Order has been shipped. Moving to Delivered state.");
context.setState(new OrderDeliveredState());
}
public void cancel(OrderContext context) {
System.out.println("Cannot cancel. Order has already been shipped.");
}
}
class OrderDeliveredState implements OrderState {
public void next(OrderContext context) {
System.out.println("Order is already delivered.");
}
public void cancel(OrderContext context) {
System.out.println("Cannot cancel. Order is already delivered.");
}
}
class OrderCancelledState implements OrderState {
public void next(OrderContext context) {
System.out.println("Cannot proceed. Order is cancelled.");
}
public void cancel(OrderContext context) {
System.out.println("Order is already cancelled.");
}
}
class OrderContext {
private OrderState currentState = new OrderPlacedState();
public void setState(OrderState state) {
this.currentState = state;
}
public void proceedToNext() {
currentState.next(this);
}
public void cancelOrder() {
currentState.cancel(this);
}
}Benefits and trade-offs
- Keeps responsibilities focused.
- Reduces conditional and tightly coupled code.
- Makes behavior easier to extend and test.
- Can introduce extra objects and interfaces.
- Too many small classes can make simple logic harder to follow.
Real-time use cases
- Traffic light systems
- E-commerce order lifecycles
- Media player states
- User authentication workflows
- Game character states
- Connection states
