DEV Community

Cover image for State Design Pattern in Java
Matheus Bernardes Spilari
Matheus Bernardes Spilari

Posted on

State Design Pattern in Java

The State Design Pattern allows an object to change its behavior when its internal state changes. This pattern is particularly useful when an object must behave differently depending on its state, without requiring modifications to the code that interacts with it.

When to Use the State Pattern?

The State Pattern is ideal when:

  • An object has multiple states, each with different behaviors.
  • Avoiding complex conditional statements (if-else or switch-case) to handle state transitions.
  • You want to encapsulate state-specific behavior in separate classes for better maintainability.

Implementing the State Pattern in Java

Let's look at a simple implementation using a vending machine that can be in different states: NoCredit, WithCredit, and DispensingBeverage.

1. State Interface

The interface defines behaviors that vary based on the machine's state.

public interface State {
    void insertCredit();
    void ejectCredit();
    void dispenseBeverage();
}
Enter fullscreen mode Exit fullscreen mode

2. Context Class (Vending Machine)

This class maintains the current state and dynamically updates it as actions are performed.

public class VendingMachine {
    private State currentState;

    private final State noCreditState = new NoCredit(this);
    private final State withCreditState = new WithCredit(this);
    private final State dispensingState = new DispensingBeverage(this);

    public VendingMachine() {
        this.currentState = noCreditState;
    }

    public void setState(State newState) {
        this.currentState = newState;
    }

    public State getNoCreditState() { return noCreditState; }
    public State getWithCreditState() { return withCreditState; }
    public State getDispensingState() { return dispensingState; }

    public void insertCredit() { currentState.insertCredit(); }
    public void ejectCredit() { currentState.ejectCredit(); }
    public void dispenseBeverage() { currentState.dispenseBeverage(); }
}
Enter fullscreen mode Exit fullscreen mode

3. State Implementations

Each state class implements the State interface and modifies the machine's state when appropriate.

// State: No Credit
class NoCredit implements State {
    private VendingMachine machine;

    public NoCredit(VendingMachine machine) { this.machine = machine; }

    @Override
    public void insertCredit() {
        System.out.println("Credit inserted. You can now buy a beverage.");
        machine.setState(machine.getWithCreditState());
    }

    @Override
    public void ejectCredit() { System.out.println("No credit to eject."); }
    @Override
    public void dispenseBeverage() { System.out.println("No credit. Cannot dispense beverage."); }
}

// State: With Credit
class WithCredit implements State {
    private VendingMachine machine;

    public WithCredit(VendingMachine machine) { this.machine = machine; }

    @Override
    public void insertCredit() { System.out.println("Credit already inserted."); }
    @Override
    public void ejectCredit() {
        System.out.println("Credit ejected. The machine now has no credit.");
        machine.setState(machine.getNoCreditState());
    }
    @Override
    public void dispenseBeverage() {
        System.out.println("Beverage dispensed. Enjoy!");
        machine.setState(machine.getDispensingState());
    }
}

// State: Dispensing Beverage
class DispensingBeverage implements State {
    private VendingMachine machine;

    public DispensingBeverage(VendingMachine machine) { this.machine = machine; }

    @Override
    public void insertCredit() { System.out.println("The machine is dispensing. Please wait."); }
    @Override
    public void ejectCredit() { System.out.println("Cannot eject credit while dispensing."); }
    @Override
    public void dispenseBeverage() {
        System.out.println("Beverage already dispensed. Waiting for next transaction.");
        machine.setState(machine.getNoCreditState());
    }
}
Enter fullscreen mode Exit fullscreen mode

4. Testing the Vending Machine

With the improved design, the state transitions happen automatically within the state classes.

public class VendingMachineTest {
    public static void main(String[] args) {
        VendingMachine machine = new VendingMachine();

        machine.insertCredit();  // Insert credit → Changes to "With Credit"
        machine.dispenseBeverage(); // Dispense beverage → Changes to "Dispensing"
        machine.dispenseBeverage(); // Complete transaction → Returns to "No Credit"
        machine.ejectCredit(); // No credit to eject
    }
}
Enter fullscreen mode Exit fullscreen mode

Expected Output

Credit inserted. You can now buy a beverage.
Beverage dispensed. Enjoy!
Beverage already dispensed. Waiting for next transaction.
No credit to eject.
Enter fullscreen mode Exit fullscreen mode

Advantages of the State Pattern

Encapsulation of Behavior – Each state has its own behavior, making the code easier to maintain.

Eliminates Complex Conditionals – Avoids large if-else or switch statements in the context class.

Easier to Add New States – You can introduce new states without modifying the existing ones.

Improved Code Readability – The logic for each state is clearly separated, making the code more organized.


Disadvantages of the State Pattern

Increased Number of Classes – Requires a new class for each state, which can lead to more complex structures.

Overhead in Simple Scenarios – If an object has only a few states, a simple switch statement may be more efficient.


Conclusion

The State Pattern is a powerful tool when dealing with objects that change behavior based on their state. By encapsulating state-specific behavior into separate classes, we create a cleaner, more maintainable, and scalable design. However, if the number of states is minimal, the overhead of additional classes may not be worth it.


📍 Reference

💻 Project Repository

👋 Talk to me

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

👋 Kindness is contagious

Explore a trove of insights in this engaging article, celebrated within our welcoming DEV Community. Developers from every background are invited to join and enhance our shared wisdom.

A genuine "thank you" can truly uplift someone’s day. Feel free to express your gratitude in the comments below!

On DEV, our collective exchange of knowledge lightens the road ahead and strengthens our community bonds. Found something valuable here? A small thank you to the author can make a big difference.

Okay