State Design Pattern in Java: Changing Object Behavior Dynamically Based on Its Current State

Description / Meta Description

Learn the State Design Pattern in Java with practical examples. Understand how State eliminates complex state-based if-else logic, enables clean state transitions, and powers workflow engines, order management systems, approval processes, ticketing systems, and enterprise applications.


State Design Pattern in Java: Changing Object Behavior Dynamically Based on Its Current State

In the previous article, we explored the Chain of Responsibility Pattern, which helps process requests through a pipeline of independent handlers.

Chain of Responsibility answers:

How do I process a request through multiple stages?

State answers a different question:

How do I change an object’s behavior when its internal state changes?

This pattern is extremely common in enterprise software.

If you’ve worked with:

  • Order Management Systems
  • Approval Workflows
  • Ticketing Platforms
  • Workflow Engines
  • Banking Applications
  • Finite State Machines

then you’ve likely encountered State Pattern.


The Problem: State-Based if-else Explosion

Imagine an e-commerce order.

An order can be:

NEW
PAID
SHIPPED
DELIVERED
CANCELLED

Different actions are allowed in different states.

For example:

NEW
  → Pay
  → Cancel

PAID
  → Ship
  → Refund

SHIPPED
  → Deliver

DELIVERED
  → Review

CANCELLED
  → No Actions

A naive implementation usually becomes:

public class Order {

    private String state;

    public void process() {

        if(state.equals("NEW")) {

            // payment logic

        } else if(state.equals("PAID")) {

            // shipping logic

        } else if(state.equals("SHIPPED")) {

            // delivery logic

        } else if(state.equals("DELIVERED")) {

            // review logic

        }
    }
}

Looks manageable initially.


Then business requirements evolve:

PARTIALLY_PAID
RETURN_REQUESTED
RETURNED
REFUNDED
ON_HOLD

Now:

if
else if
else if
else if
else if
else if
else if

The class becomes difficult to maintain.

Every new state requires modifying existing code.

This violates:

Open/Closed Principle

What We Really Want

Instead of:

Order
  │
  └── Huge State Logic

We want:

Order
   │
   ▼

Current State

   │
   ├── New State
   ├── Paid State
   ├── Shipped State
   └── Delivered State

Each state owns its behavior.

The order simply delegates.

This is State Pattern.


What is State Pattern?

State is a Behavioral Design Pattern that:

Allows an object to alter its behavior when its internal state changes.

In simple terms:

Same Object

Different Behavior

Based On Current State

The object appears to change its class.


Real Life Analogy

Consider a traffic signal.

Current state:

GREEN

Behavior:

Vehicles Move

Change state:

RED

Behavior:

Vehicles Stop

The traffic signal remains the same object.

Only its state changes.

Its behavior changes automatically.


State Architecture

Context
   │
   ▼

State Interface

   ▲
   │

New State
Paid State
Shipped State
Delivered State

Context delegates behavior to the current state.


Step 1: Create State Interface

public interface OrderState {

    void processOrder(
            OrderContext context);
}

Every state must implement behavior.


Step 2: Create Context

public class OrderContext {

    private OrderState state;

    public OrderContext(
            OrderState state) {

        this.state = state;
    }

    public void setState(
            OrderState state) {

        this.state = state;
    }

    public void process() {

        state.processOrder(this);
    }
}

Context stores the current state.


Step 3: Create New State

public class NewState
        implements OrderState {

    @Override
    public void processOrder(
            OrderContext context) {

        System.out.println(
            "Processing Payment");

        context.setState(
            new PaidState());
    }
}

Notice:

State Transition

happens inside the state itself.


Step 4: Create Paid State

public class PaidState
        implements OrderState {

    @Override
    public void processOrder(
            OrderContext context) {

        System.out.println(
            "Shipping Order");

        context.setState(
            new ShippedState());
    }
}

Step 5: Create Shipped State

public class ShippedState
        implements OrderState {

    @Override
    public void processOrder(
            OrderContext context) {

        System.out.println(
            "Delivering Order");

        context.setState(
            new DeliveredState());
    }
}

Step 6: Create Delivered State

public class DeliveredState
        implements OrderState {

    @Override
    public void processOrder(
            OrderContext context) {

        System.out.println(
            "Order Complete");
    }
}

Client Usage

OrderContext order =
        new OrderContext(
            new NewState());

order.process();
order.process();
order.process();
order.process();

Output:

Processing Payment
Shipping Order
Delivering Order
Order Complete

Same object.

Different behavior.

Different state.


Why State Works

Without State:

Order
 │
 ├── if NEW
 ├── if PAID
 ├── if SHIPPED
 ├── if DELIVERED
 └── if CANCELLED

Huge conditionals.


With State:

Order
  │
  ▼

Current State

  │
  ▼

State Object

Behavior remains isolated.


Order Lifecycle Visualization

NEW
 │
 ▼
PAID
 │
 ▼
SHIPPED
 │
 ▼
DELIVERED

Each node becomes a separate state class.


Enterprise Example: Loan Approval Workflow

Loan application states:

SUBMITTED
 │
 ▼
UNDER_REVIEW
 │
 ▼
APPROVED
 │
 ▼
DISBURSED

Different actions allowed at each stage.

State Pattern models this naturally.


Ticketing System Example

Support ticket:

OPEN
 │
 ▼
IN_PROGRESS
 │
 ▼
RESOLVED
 │
 ▼
CLOSED

Behavior changes automatically.

For example:

CLOSED Ticket

Cannot Be Assigned
Cannot Be Edited

State controls behavior.


ATM Machine Example

Classic GoF example.

States:

NO_CARD
CARD_INSERTED
PIN_VERIFIED
TRANSACTION_IN_PROGRESS

Each state supports different operations.

Example:

NO_CARD

Insert Card
✓

Withdraw Cash
✗

State Pattern makes this easy.


Spring State Machine

Spring provides:

spring-statemachine

for implementing workflow systems.

Example:

ORDER_CREATED
ORDER_PAID
ORDER_SHIPPED
ORDER_DELIVERED

Exactly State Pattern at enterprise scale.


State vs Strategy

Most common interview question.

The structures look similar.


Strategy

Purpose:

Choose Behavior

Client chooses algorithm.

Example:

UPI Payment
Card Payment
Wallet Payment

State

Purpose:

Behavior Changes Automatically

Based on current state.

Example:

NEW
PAID
SHIPPED

State transitions itself.


Visual Difference

Strategy:

Client
  │
  ▼

Choose Strategy

State:

Current State
      │
      ▼

Changes To
Next State

Automatically.


State vs Chain of Responsibility

Chain

Request flows through:

Handler 1
Handler 2
Handler 3

State

Object changes behavior:

State 1
   ↓
State 2
   ↓
State 3

Different concepts.


Benefits of State Pattern

1. Eliminates Large if-else Blocks

Primary benefit.


2. Open/Closed Principle

Add new states easily.


3. Cleaner Code

State-specific logic remains isolated.


4. Easier Testing

Each state can be tested independently.


5. Natural Workflow Modeling

Perfect for business processes.


Common Mistakes

Mistake 1: Too Many States

If only:

2 States

exist, State Pattern may be overkill.


Mistake 2: Transition Logic Everywhere

Keep transitions inside states.

Avoid scattering transition rules.


Mistake 3: Confusing State With Status Fields

Simply having:

String status;

is not State Pattern.

State Pattern delegates behavior to state objects.


When Should You Use State Pattern?

Use State when:

✔ Behavior changes based on status

✔ Large state-based conditionals exist

✔ Workflow systems exist

✔ State transitions are important

✔ Business processes have lifecycles

Examples:

  • Order Processing
  • Ticket Management
  • Approval Systems
  • Banking Workflows
  • ATM Machines
  • Workflow Engines

Avoid State when:

❌ Only one or two states exist

❌ Behavior never changes

❌ Simpler conditionals are sufficient


Behavioral Patterns Covered So Far

PatternPurpose
StrategyChoose algorithms dynamically
ObserverPublish-subscribe communication
CommandEncapsulate actions
Chain of ResponsibilityProcess requests through handlers
StateChange behavior based on state

Behavioral Pattern Cheat Sheet

Strategy
→ Choose Behavior

Observer
→ Broadcast Events

Command
→ Encapsulate Actions

Chain of Responsibility
→ Process Through Pipeline

State
→ Change Behavior Based On State

Final Thoughts

The State Pattern solves a common enterprise challenge:

How do we manage complex workflows and state transitions without filling our codebase with endless conditional logic?

By moving behavior into dedicated state objects, we gain:

  • Better maintainability
  • Cleaner workflows
  • Easier testing
  • Flexible state transitions
  • Improved scalability

This is why State Pattern powers:

  • Order Management Systems
  • Workflow Engines
  • Ticketing Platforms
  • Banking Applications
  • Approval Systems
  • Spring State Machine

Whenever you encounter an object whose behavior depends heavily on its current status, State Pattern is often the ideal solution.

In the next article, we’ll explore:

Template Method Design Pattern — Defining the Skeleton of an Algorithm While Allowing Subclasses to Customize Individual Steps

You’ll learn how frameworks, batch processing systems, Spring components, and enterprise workflows use Template Method to standardize processes while preserving flexibility.

Leave a Reply

Your email address will not be published. Required fields are marked *