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.