Implementing Simple State Machines with Java Enums

1. Overview

In this tutorial, we’ll have a look at State Machines and how they can
be implemented in Java using Enums.

We’ll also explain the advantages of this implementation compared to
using an interface and a concrete class for each state.

2. Java Enums

A Java Enum is a special
type of class that defines a list of constants. This allows for
type-safe implementation and more readable code.

As an example, let’s suppose we have an HR software system that can
approve leave requests submitted by employees. This request is reviewed
by the Team Leader, who escalates it to the Department Manager. The
Department Manager is the person responsible for approving the request.

The simplest enum that holds the states of a leave request is:

public enum LeaveRequestState {
    Submitted,
    Escalated,
    Approved
}

We can refer to the constants of this enum:

LeaveRequestState state = LeaveRequestState.Submitted;

Enums can also contain methods. We can write an abstract method in an
enum, which will force every enum instance to implement this method.
This is very important for the implementation of state machines, as
we’ll see below.

Since Java enums implicitly extend the class java.lang.Enum, they
can’t extend another class. However, they can implement an interface,
just like any other class.

Here’s an example of an enum containing an abstract method:

public enum LeaveRequestState {
    Submitted {
        @Override
        public String responsiblePerson() {
            return "Employee";
        }
    },
    Escalated {
        @Override
        public String responsiblePerson() {
            return "Team Leader";
        }
    },
    Approved {
        @Override
        public String responsiblePerson() {
            return "Department Manager";
        }
    };

    public abstract String responsiblePerson();
}

Note the usage of the semicolon at the end of the last enum constant.
The semicolon is required when we have one or more methods following the
constants.

In this case, we extended the first example with a responsiblePerson()
method. This tells us the person responsible for performing each action.
So, if we try to check the person responsible for the Escalated state,
it will give us “Team Leader”:

LeaveRequestState state = LeaveRequestState.Escalated;
assertEquals("Team Leader", state.responsiblePerson());

In the same way, if we check who is responsible for approving the
request, it will give us “Department Manager”:

LeaveRequestState state = LeaveRequestState.Approved;
assertEquals("Department Manager", state.responsiblePerson());

3. State Machines

A state machine — also called a finite state machine or finite
automaton — is a computational model used to build an abstract machine.
These machines can only be in one state at a given time. Each state is
a status of the system that changes to another state. These state
changes are called transitions.

It can get complicated in mathematics with diagrams and notations, but
things are a lot easier for us programmers.

The State Pattern is
one of the well-known twenty-three design patterns of the GoF. This
pattern borrows the concept from the model in mathematics. It allows an
object to encapsulate different behaviors for the same object, based on
its state. We can program the transition between states and later define
separate states.

To explain the concept better, we’ll expand our leave request example to
implement a state machine.

4. Enums as State Machines

We’ll focus on the enum implementation of state machines in Java.
Other
implementations
are possible, and we’ll compare them in the next
section.

The main point of state machine implementation using an enum is that we
don’t have to deal with explicitly setting the states
. Instead, we can
just provide the logic on how to transition from one state to the next
one. Let’s dive right in:

public enum LeaveRequestState {

    Submitted {
        @Override
        public LeaveRequestState nextState() {
            return Escalated;
        }

        @Override
        public String responsiblePerson() {
            return "Employee";
        }
    },
    Escalated {
        @Override
        public LeaveRequestState nextState() {
            return Approved;
        }

        @Override
        public String responsiblePerson() {
            return "Team Leader";
        }
    },
    Approved {
        @Override
        public LeaveRequestState nextState() {
            return this;
        }

        @Override
        public String responsiblePerson() {
            return "Department Manager";
        }
    };

    public abstract LeaveRequestState nextState();
    public abstract String responsiblePerson();
}

In this example, the state machine transitions are implemented using
the enum’s abstract methods
. More precisely, using the nextState() on
each enum constant, we specify the transition to the next state. If
needed, we can also implement a previousState() method.

Below is a test to check our implementation:

LeaveRequestState state = LeaveRequestState.Submitted;

state = state.nextState();
assertEquals(LeaveRequestState.Escalated, state);

state = state.nextState();
assertEquals(LeaveRequestState.Approved, state);

state = state.nextState();
assertEquals(LeaveRequestState.Approved, state);

We start the leave request in the Submitted initial state. We then
verify the state transitions by using the nextState() method we
implemented above.

Note that since Approved is the final state, no other transition can
happen
.

5. Advantages of Implementing State Machines with Java Enums

The implementation of
state machines
with interfaces and implementation classes can be a
significant amount of code to develop and maintain.

Since a Java enum is, in its simplest form, a list of constants, we can
use an enum to define our states. And since an enum can also contain
behavior, we can use methods to provide the transition implementation
between states.

Having all the logic in a simple enum allows for a clean and
straightforward solution.

6. Conclusion

In this article, we looked at state machines and how they can be
implemented in Java using Enums. We gave an example and tested it.

Eventually, we also discussed the advantages of using enums to implement
state machines. As an alternative to the interface and implementation
solution, enums provide a cleaner and easier-to-understand
implementation of state machines.

As always, all of the code snippets mentioned in this article can be
found in on our
GitHub
repository
.

Leave a Reply

Your email address will not be published.