State becomes useful when an object’s behavior changes meaningfully based on its current lifecycle stage.
Without it, classes often fill up with conditionals like if (status == PAID && action == SHIP).
Example Problem
Orders move through states:
- new
- paid
- shipped
- delivered
- cancelled
Valid actions depend on the current state.
UML
stateDiagram-v2
[*] --> New
New --> Paid: pay
New --> Cancelled: cancel
Paid --> Shipped: ship
Paid --> Cancelled: refundCancel
Shipped --> Delivered: deliver
Implementation Walkthrough
public interface OrderState {
void pay(OrderContext context);
void ship(OrderContext context);
void deliver(OrderContext context);
void cancel(OrderContext context);
}
public final class OrderContext {
private OrderState state = new NewState();
void transitionTo(OrderState state) {
this.state = state;
}
public void pay() { state.pay(this); }
public void ship() { state.ship(this); }
public void deliver() { state.deliver(this); }
public void cancel() { state.cancel(this); }
}
public final class NewState implements OrderState {
public void pay(OrderContext context) { context.transitionTo(new PaidState()); }
public void ship(OrderContext context) { throw new IllegalStateException("Pay before ship"); }
public void deliver(OrderContext context) { throw new IllegalStateException("Ship before deliver"); }
public void cancel(OrderContext context) { context.transitionTo(new CancelledState()); }
}
public final class PaidState implements OrderState {
public void pay(OrderContext context) { throw new IllegalStateException("Already paid"); }
public void ship(OrderContext context) { context.transitionTo(new ShippedState()); }
public void deliver(OrderContext context) { throw new IllegalStateException("Ship before deliver"); }
public void cancel(OrderContext context) { context.transitionTo(new CancelledState()); }
}
Usage:
OrderContext order = new OrderContext();
order.pay();
order.ship();
order.deliver();
The main advantage here is that each state owns the rules for what is legal next. That makes invalid transitions hard to ignore and keeps lifecycle logic close to the state that understands it best.
Why State Improves the Model
Each state owns the rules that apply in that state.
That is easier to reason about than one giant switch inside Order.
It also makes transitions explicit, which is valuable in workflow-heavy systems.
That explicitness becomes even more useful once you add auditing, notifications, or compensation around state changes, because the transition points are already modeled clearly.
Practical Guidance
State is worth introducing when:
- lifecycle rules are non-trivial
- invalid transitions matter
- behavior differs across stages
If state is little more than display text, an enum is enough.