Spring State Machine: what is it and do you need it?

State machine is a model of computation based on the finite states, as Wikipedia very obligingly says. Usually there are workflows to go with the states, meaning that you can’t just go from any state to any other state: there’re rules one should follow. The transitions between these states are limited by the rules.

The Spring framework has a whole library called Spring State Machine. It is an implementation of the concept, intended to simplify the development of the state machine logic for developers, already using Spring framework.

Let’s see how it works.

First we need a Spring Boot application with the dependency on Spring State Machine (and Lombok for simplicity). It’s very easy to generate one from the Spring Starter page or from the IDE like Intellij IDEA which also uses Spring starter templates).

To actually use the state machine, it should be enabled in the application class:

When the @EnableStateMachine annotation is used, it automatically creates a default state machine when the application starts. So it can be injected into the Application class. By default, the bean will be called stateMachine, but it can be given another name. We will also need classes for our events and for the states. Let’s base our simple example on a library. We know that the library books can be borrowed or returned, or maybe damaged and in repair (therefore unable to borrow). So, this is exactly what we put into the model.

Then, the state machine should be configured with these transactions and states:

And the last but not least, we allow the state machine to automatically start (it won’t do that by default).

Now we can use it in the application and see what happens!

When we run the application, we see the following in the logs:

2018-07-07 13:46:05.096 INFO 37417 --- [ main] STATE MACHINE : return accepted: false
2018-07-07 13:46:05.098 INFO 37417 --- [ main] STATE MACHINE : borrow accepted: true

I intentionally called RETURN first to see that it will fail. However, it fails with no exceptions: the action was just not accepted and the machine stayed in the AVAILABLE state, which made it possible to execute BORROW again. So, what happens if we swap the two calls?

2018-07-07 13:49:46.218 INFO 37496 --- [ main] STATE MACHINE : borrow accepted: true
2018-07-07 13:49:46.218 INFO 37496 --- [ main] STATE MACHINE : return accepted: true

Which means that the correct interaction is accepted. However, what if we want more visibility on what happens? One way is to configure handlers for our state changes:

2018-07-07 13:53:59.940 INFO 37579 --- [ main] STATE MACHINE : Entry action null to get from EMPTY STATE to AVAILABLE
2018-07-07 13:54:00.051 INFO 37579 --- [ main] STATE MACHINE : return accepted: false
2018-07-07 13:54:00.052 INFO 37579 --- [ main] STATE MACHINE : Exit action BORROW to get from AVAILABLE to BORROWED
2018-07-07 13:54:00.052 INFO 37579 --- [ main] STATE MACHINE : Entry action BORROW to get from AVAILABLE to BORROWED
2018-07-07 13:54:00.053 INFO 37579 --- [ main] STATE MACHINE : borrow accepted: true
2018-07-07 13:54:00.053 INFO 37579 --- [ main] STATE MACHINE : Exit action RETURN to get from BORROWED to AVAILABLE
2018-07-07 13:54:00.053 INFO 37579 --- [ main] STATE MACHINE : Entry action RETURN to get from BORROWED to AVAILABLE
2018-07-07 13:54:00.053 INFO 37579 --- [ main] STATE MACHINE : return accepted: true

The other way would be to define a full-blown listener:

And link the listener to the machine when it’s configured. Now we can remove our entry and exit listeners and the states configuration will return to our first revision (see above).

This way, you will have much more insight in what’s happening:

2018-07-07 13:59:22.714 INFO 37684 --- [ main] STATE MACHINE : Entered state AVAILABLE
2018-07-07 13:59:22.716 INFO 37684 --- [ main] STATE MACHINE : State changed from EMPTY STATE to AVAILABLE
2018-07-07 13:59:22.717 INFO 37684 --- [ main] STATE MACHINE : Machine started: IN_REPAIR AVAILABLE BORROWED / AVAILABLE / uuid=815f744e-8c5c-4ab1-88d1-b5223199bc4e / id=null
2018-07-07 13:59:22.835 ERROR 37684 --- [ main] STATE MACHINE : Event not accepted: RETURN
2018-07-07 13:59:22.836 INFO 37684 --- [ main] STATE MACHINE : return accepted: false
2018-07-07 13:59:22.837 INFO 37684 --- [ main] STATE MACHINE : Exited state AVAILABLE
2018-07-07 13:59:22.838 INFO 37684 --- [ main] STATE MACHINE : Entered state BORROWED
2018-07-07 13:59:22.838 INFO 37684 --- [ main] STATE MACHINE : State changed from AVAILABLE to BORROWED
2018-07-07 13:59:22.839 INFO 37684 --- [ main] STATE MACHINE : borrow accepted: true
2018-07-07 13:59:22.839 INFO 37684 --- [ main] STATE MACHINE : Exited state BORROWED
2018-07-07 13:59:22.839 INFO 37684 --- [ main] STATE MACHINE : Entered state AVAILABLE
2018-07-07 13:59:22.839 INFO 37684 --- [ main] STATE MACHINE : State changed from BORROWED to AVAILABLE
2018-07-07 13:59:22.839 INFO 37684 --- [ main] STATE MACHINE : return accepted: true

When is the state machine needed? Spring documentations states that you are already trying to implement a state machine if:

  • Using boolean flags or enums to model situations.
  • Having variables which only have meaning for some part of your application lifecycle.
  • Looping through if/else structure and checking if a particular flag or enum is set and then making further exceptions about what to do when certain combinations of your flags and enums exist or don’t exist together.

I can think of a few examples:

  • Bots. This is usually a great case for a state machine, because a bot usually has only a few states with different actions in between. For example, you have a bot who’s asking questions to book a hotel (a well-known example). You ask a few questions: location,  number of guests, price range etc. Every question is a state. Every answer is an event that allows to transition into the next state.
  • IOT. The simplest state machine has two states: ON and OFF. But with more complex devices than, say, a light switch, there can be more states in between, and more events to make a state transition.

There’s much more that the Spring State Machine can do. For example, the states can be nested. Also, there’s guards that can be configured to check if the transition is allowed or not, and pseudo states that allow to define choice state, junction state etc. The events can be triggered by the actions, or on a timer. The state machines can be persisted to make them more performant. To navigate all that, you need to study Spring State Machine documentation and determine what of it fits your specific case. Here, we only very lightly scratched the surface.

You can watch a video about Spring State Machine, or study the full specification to find out more about the topic.

The project sources for this article can be found here.

Advertisements

About Maryna Cherniavska

I have productively spent 10+ years in IT industry, designing, developing, building and deploying desktop and web applications, designing database structures and otherwise proving that females have a place among software developers. And this is a good place.
This entry was posted in java and tagged , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s