diff --git a/pom.xml b/pom.xml
index 15e0e3322e..7c1cacbd33 100644
--- a/pom.xml
+++ b/pom.xml
@@ -188,6 +188,7 @@
spring-sleuth
spring-social-login
spring-spel
+ spring-state-machine
spring-thymeleaf
spring-userservice
spring-zuul
@@ -210,7 +211,9 @@
rabbitmq
vertx
-
+
+
+
diff --git a/spring-state-machine/bpmn/forkjoin.bpmn b/spring-state-machine/bpmn/forkjoin.bpmn
new file mode 100644
index 0000000000..0cb060f74b
--- /dev/null
+++ b/spring-state-machine/bpmn/forkjoin.bpmn
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-state-machine/bpmn/img/forkjoin.png b/spring-state-machine/bpmn/img/forkjoin.png
new file mode 100644
index 0000000000..642ab6949d
Binary files /dev/null and b/spring-state-machine/bpmn/img/forkjoin.png differ
diff --git a/spring-state-machine/bpmn/img/simple.png b/spring-state-machine/bpmn/img/simple.png
new file mode 100644
index 0000000000..7a79bf1d89
Binary files /dev/null and b/spring-state-machine/bpmn/img/simple.png differ
diff --git a/spring-state-machine/bpmn/simple.bpmn b/spring-state-machine/bpmn/simple.bpmn
new file mode 100644
index 0000000000..8ed463e9f9
--- /dev/null
+++ b/spring-state-machine/bpmn/simple.bpmn
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spring-state-machine/pom.xml b/spring-state-machine/pom.xml
new file mode 100644
index 0000000000..5393626083
--- /dev/null
+++ b/spring-state-machine/pom.xml
@@ -0,0 +1,31 @@
+
+
+
+ parent-modules
+ com.baeldung
+ 1.0.0-SNAPSHOT
+
+ 4.0.0
+
+ baeldung-spring-state-machine
+
+ 1.8
+ 1.8
+
+
+
+
+ org.springframework.statemachine
+ spring-statemachine-core
+ 1.2.3.RELEASE
+
+
+ junit
+ junit
+ 4.11
+ test
+
+
+
\ No newline at end of file
diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewEvents.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewEvents.java
new file mode 100644
index 0000000000..971fc5dde7
--- /dev/null
+++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewEvents.java
@@ -0,0 +1,5 @@
+package com.baeldung.spring.stateMachine.applicationReview;
+
+public enum ApplicationReviewEvents {
+ APPROVE, REJECT
+}
diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewStates.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewStates.java
new file mode 100644
index 0000000000..1df2db1f86
--- /dev/null
+++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/applicationreview/ApplicationReviewStates.java
@@ -0,0 +1,5 @@
+package com.baeldung.spring.stateMachine.applicationReview;
+
+public enum ApplicationReviewStates {
+ PEER_REVIEW, PRINCIPAL_REVIEW, APPROVED, REJECTED
+}
diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/ForkJoinStateMachineConfiguration.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/ForkJoinStateMachineConfiguration.java
new file mode 100644
index 0000000000..c55104a627
--- /dev/null
+++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/ForkJoinStateMachineConfiguration.java
@@ -0,0 +1,74 @@
+package com.baeldung.spring.stateMachine.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.statemachine.config.EnableStateMachine;
+import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
+import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
+import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
+import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
+import org.springframework.statemachine.guard.Guard;
+
+@Configuration
+@EnableStateMachine
+public class ForkJoinStateMachineConfiguration extends StateMachineConfigurerAdapter {
+
+ @Override
+ public void configure(StateMachineConfigurationConfigurer config)
+ throws Exception {
+ config
+ .withConfiguration()
+ .autoStartup(true)
+ .listener(new StateMachineListener());
+ }
+
+ @Override
+ public void configure(StateMachineStateConfigurer states) throws Exception {
+ states
+ .withStates()
+ .initial("SI")
+ .fork("SFork")
+ .join("SJoin")
+ .end("SF")
+ .and()
+ .withStates()
+ .parent("SFork")
+ .initial("Sub1-1")
+ .end("Sub1-2")
+ .and()
+ .withStates()
+ .parent("SFork")
+ .initial("Sub2-1")
+ .end("Sub2-2");
+ }
+
+ @Override
+ public void configure(StateMachineTransitionConfigurer transitions) throws Exception {
+ transitions.withExternal()
+ .source("SI").target("SFork").event("E1")
+ .and().withExternal()
+ .source("Sub1-1").target("Sub1-2").event("sub1")
+ .and().withExternal()
+ .source("Sub2-1").target("Sub2-2").event("sub2")
+ .and()
+ .withFork()
+ .source("SFork")
+ .target("Sub1-1")
+ .target("Sub2-1")
+ .and()
+ .withJoin()
+ .source("Sub1-2")
+ .source("Sub2-2")
+ .target("SJoin");
+ }
+
+ @Bean
+ public Guard mediumGuard() {
+ return (ctx) -> false;
+ }
+
+ @Bean
+ public Guard highGuard() {
+ return (ctx) -> false;
+ }
+}
\ No newline at end of file
diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/HierarchicalStateMachineConfiguration.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/HierarchicalStateMachineConfiguration.java
new file mode 100644
index 0000000000..708dbd3077
--- /dev/null
+++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/HierarchicalStateMachineConfiguration.java
@@ -0,0 +1,47 @@
+package com.baeldung.spring.stateMachine.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.statemachine.config.EnableStateMachine;
+import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
+import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
+import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
+import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
+
+@Configuration
+@EnableStateMachine
+public class HierarchicalStateMachineConfiguration extends StateMachineConfigurerAdapter {
+
+ @Override
+ public void configure(StateMachineConfigurationConfigurer config)
+ throws Exception {
+ config
+ .withConfiguration()
+ .autoStartup(true)
+ .listener(new StateMachineListener());
+ }
+
+ @Override
+ public void configure(StateMachineStateConfigurer states) throws Exception {
+ states
+ .withStates()
+ .initial("SI")
+ .state("SI")
+ .end("SF")
+ .and()
+ .withStates()
+ .parent("SI")
+ .initial("SUB1")
+ .state("SUB2")
+ .end("SUBEND");
+ }
+
+ @Override
+ public void configure(StateMachineTransitionConfigurer transitions) throws Exception {
+ transitions.withExternal()
+ .source("SI").target("SF").event("end")
+ .and().withExternal()
+ .source("SUB1").target("SUB2").event("se1")
+ .and().withExternal()
+ .source("SUB2").target("SUBEND").event("s-end");
+ }
+}
\ No newline at end of file
diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/JunctionStateMachineConfiguration.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/JunctionStateMachineConfiguration.java
new file mode 100644
index 0000000000..e1bae10fb7
--- /dev/null
+++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/JunctionStateMachineConfiguration.java
@@ -0,0 +1,60 @@
+package com.baeldung.spring.stateMachine.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.statemachine.config.EnableStateMachine;
+import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
+import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
+import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
+import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
+import org.springframework.statemachine.guard.Guard;
+
+@Configuration
+@EnableStateMachine
+public class JunctionStateMachineConfiguration extends StateMachineConfigurerAdapter {
+
+ @Override
+ public void configure(StateMachineConfigurationConfigurer config)
+ throws Exception {
+ config
+ .withConfiguration()
+ .autoStartup(true)
+ .listener(new StateMachineListener());
+ }
+
+ @Override
+ public void configure(StateMachineStateConfigurer states) throws Exception {
+ states
+ .withStates()
+ .initial("SI")
+ .junction("SJ")
+ .state("high")
+ .state("medium")
+ .state("low")
+ .end("SF");
+ }
+
+ @Override
+ public void configure(StateMachineTransitionConfigurer transitions) throws Exception {
+ transitions.withExternal()
+ .source("SI").target("SJ").event("E1")
+ .and()
+ .withJunction()
+ .source("SJ")
+ .first("high", highGuard())
+ .then("medium", mediumGuard())
+ .last("low")
+ .and().withExternal()
+ .source("low").target("SF").event("end");
+ }
+
+ @Bean
+ public Guard mediumGuard() {
+ return (ctx) -> false;
+ }
+
+ @Bean
+ public Guard highGuard() {
+ return (ctx) -> false;
+ }
+}
\ No newline at end of file
diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleEnumStateMachineConfiguration.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleEnumStateMachineConfiguration.java
new file mode 100644
index 0000000000..4e11851644
--- /dev/null
+++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleEnumStateMachineConfiguration.java
@@ -0,0 +1,53 @@
+package com.baeldung.spring.stateMachine.config;
+
+import com.baeldung.spring.stateMachine.applicationReview.ApplicationReviewEvents;
+import com.baeldung.spring.stateMachine.applicationReview.ApplicationReviewStates;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.statemachine.action.Action;
+import org.springframework.statemachine.config.EnableStateMachine;
+import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
+import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
+import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
+import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
+import org.springframework.statemachine.guard.Guard;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+@Configuration
+@EnableStateMachine
+public class SimpleEnumStateMachineConfiguration extends StateMachineConfigurerAdapter {
+
+ @Override
+ public void configure(StateMachineConfigurationConfigurer config)
+ throws Exception {
+ config
+ .withConfiguration()
+ .autoStartup(true)
+ .listener(new StateMachineListener());
+ }
+
+ @Override
+ public void configure(StateMachineStateConfigurer states) throws Exception {
+ states
+ .withStates()
+ .initial(ApplicationReviewStates.PEER_REVIEW)
+ .state(ApplicationReviewStates.PRINCIPAL_REVIEW)
+ .end(ApplicationReviewStates.APPROVED)
+ .end(ApplicationReviewStates.REJECTED);
+
+ }
+
+ @Override
+ public void configure(StateMachineTransitionConfigurer transitions) throws Exception {
+ transitions.withExternal()
+ .source(ApplicationReviewStates.PEER_REVIEW).target(ApplicationReviewStates.PRINCIPAL_REVIEW).event(ApplicationReviewEvents.APPROVE)
+ .and().withExternal()
+ .source(ApplicationReviewStates.PRINCIPAL_REVIEW).target(ApplicationReviewStates.APPROVED).event(ApplicationReviewEvents.APPROVE)
+ .and().withExternal()
+ .source(ApplicationReviewStates.PEER_REVIEW).target(ApplicationReviewStates.REJECTED).event(ApplicationReviewEvents.REJECT)
+ .and().withExternal()
+ .source(ApplicationReviewStates.PRINCIPAL_REVIEW).target(ApplicationReviewStates.REJECTED).event(ApplicationReviewEvents.REJECT);
+ }
+}
\ No newline at end of file
diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleStateMachineConfiguration.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleStateMachineConfiguration.java
new file mode 100644
index 0000000000..fe4e0f82ce
--- /dev/null
+++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/SimpleStateMachineConfiguration.java
@@ -0,0 +1,105 @@
+package com.baeldung.spring.stateMachine.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.statemachine.action.Action;
+import org.springframework.statemachine.config.EnableStateMachine;
+import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
+import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
+import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
+import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
+import org.springframework.statemachine.guard.Guard;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.logging.Logger;
+
+@Configuration
+@EnableStateMachine
+public class SimpleStateMachineConfiguration extends StateMachineConfigurerAdapter {
+
+ public static final Logger LOGGER = Logger.getLogger(SimpleStateMachineConfiguration.class.getName());
+
+ @Override
+ public void configure(StateMachineConfigurationConfigurer config)
+ throws Exception {
+ config
+ .withConfiguration()
+ .autoStartup(true)
+ .listener(new StateMachineListener());
+ }
+
+ @Override
+ public void configure(StateMachineStateConfigurer states) throws Exception {
+ states
+ .withStates()
+ .initial("SI")
+ .end("SF")
+ .states(new HashSet<>(Arrays.asList("S1", "S2")))
+ .state("S4", executeAction(), errorAction())
+ .stateEntry("S3", entryAction())
+ .stateDo("S3", executeAction())
+ .stateExit("S3", exitAction());
+
+ }
+
+ @Override
+ public void configure(StateMachineTransitionConfigurer transitions) throws Exception {
+ transitions.withExternal()
+ .source("SI").target("S1").event("E1").action(initAction())
+ .and().withExternal()
+ .source("S1").target("S2").event("E2")
+ .and().withExternal()
+ .source("SI").target("S3").event("E3")
+ .and().withExternal()
+ .source("S3").target("S4").event("E4").guard(simpleGuard())
+ .and().withExternal()
+ .source("S2").target("SF").event("end");
+ }
+
+ @Bean
+ public Guard simpleGuard() {
+ return (ctx) -> {
+ int approvalCount = (int) ctx.getExtendedState().getVariables().getOrDefault("approvalCount", 0);
+ return approvalCount > 0;
+ };
+ }
+
+ @Bean
+ public Action entryAction() {
+ return (ctx) -> {
+ LOGGER.info("Entry " + ctx.getTarget().getId());
+ };
+ }
+
+ @Bean
+ public Action executeAction() {
+ return (ctx) -> {
+ LOGGER.info("Do " + ctx.getTarget().getId());
+ int approvals = (int) ctx.getExtendedState().getVariables().getOrDefault("approvalCount", 0);
+ approvals++;
+ ctx.getExtendedState().getVariables().put("approvalCount", approvals);
+ };
+ }
+
+ @Bean
+ public Action exitAction() {
+ return (ctx) -> {
+ LOGGER.info("Exit " + ctx.getSource().getId() + " -> " + ctx.getTarget().getId());
+ };
+ }
+
+ @Bean
+ public Action errorAction() {
+ return (ctx) -> {
+ LOGGER.info("Error " + ctx.getSource().getId() + ctx.getException());
+ };
+ }
+
+ @Bean
+ public Action initAction() {
+ return (ctx) -> {
+ LOGGER.info(ctx.getTarget().getId());
+ };
+ }
+}
\ No newline at end of file
diff --git a/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/StateMachineListener.java b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/StateMachineListener.java
new file mode 100644
index 0000000000..bb7859c683
--- /dev/null
+++ b/spring-state-machine/src/main/java/com/baeldung/spring/statemachine/config/StateMachineListener.java
@@ -0,0 +1,16 @@
+package com.baeldung.spring.stateMachine.config;
+
+import org.springframework.statemachine.listener.StateMachineListenerAdapter;
+import org.springframework.statemachine.state.State;
+
+import java.util.logging.Logger;
+
+public class StateMachineListener extends StateMachineListenerAdapter {
+
+ public static final Logger LOGGER = Logger.getLogger(StateMachineListener.class.getName());
+
+ @Override
+ public void stateChanged(State from, State to) {
+ LOGGER.info(String.format("Transitioned from %s to %s%n", from == null ? "none" : from.getId(), to.getId()));
+ }
+}
diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java
new file mode 100644
index 0000000000..416da5f0fe
--- /dev/null
+++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/ForkJoinStateMachineTest.java
@@ -0,0 +1,45 @@
+package com.baeldung.spring.stateMachine;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.statemachine.StateMachine;
+
+import com.baeldung.spring.stateMachine.config.ForkJoinStateMachineConfiguration;
+
+public class ForkJoinStateMachineTest {
+
+ @Test
+ public void whenForkStateEntered_thenMultipleSubStatesEntered() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ForkJoinStateMachineConfiguration.class);
+ StateMachine stateMachine = ctx.getBean(StateMachine.class);
+ stateMachine.start();
+
+ boolean success = stateMachine.sendEvent("E1");
+
+ assertTrue(success);
+
+ assertTrue(Arrays.asList("SFork", "Sub1-1", "Sub2-1").containsAll(stateMachine.getState().getIds()));
+ }
+
+ @Test
+ public void whenAllConfiguredJoinEntryStatesAreEntered_thenTransitionToJoinState() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ForkJoinStateMachineConfiguration.class);
+ StateMachine stateMachine = ctx.getBean(StateMachine.class);
+ stateMachine.start();
+
+ boolean success = stateMachine.sendEvent("E1");
+
+ assertTrue(success);
+
+ assertTrue(Arrays.asList("SFork", "Sub1-1", "Sub2-1").containsAll(stateMachine.getState().getIds()));
+
+ assertTrue(stateMachine.sendEvent("sub1"));
+ assertTrue(stateMachine.sendEvent("sub2"));
+ assertEquals("SJoin", stateMachine.getState().getId());
+ }
+}
diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java
new file mode 100644
index 0000000000..3557a63211
--- /dev/null
+++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/HierarchicalStateMachineTest.java
@@ -0,0 +1,37 @@
+package com.baeldung.spring.stateMachine;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.statemachine.StateMachine;
+
+import com.baeldung.spring.stateMachine.config.HierarchicalStateMachineConfiguration;
+
+public class HierarchicalStateMachineTest {
+
+ @Test
+ public void whenTransitionToSubMachine_thenSubStateIsEntered() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(HierarchicalStateMachineConfiguration.class);
+ StateMachine stateMachine = ctx.getBean(StateMachine.class);
+ stateMachine.start();
+
+
+ assertEquals(Arrays.asList("SI", "SUB1"), stateMachine.getState().getIds());
+
+ stateMachine.sendEvent("se1");
+
+ assertEquals(Arrays.asList("SI", "SUB2"), stateMachine.getState().getIds());
+
+ stateMachine.sendEvent("s-end");
+
+ assertEquals(Arrays.asList("SI", "SUBEND"), stateMachine.getState().getIds());
+
+ stateMachine.sendEvent("end");
+
+ assertEquals(1, stateMachine.getState().getIds().size());
+ assertEquals("SF", stateMachine.getState().getId());
+ }
+}
diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java
new file mode 100644
index 0000000000..d0c1225c9b
--- /dev/null
+++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/JunctionStateMachineTest.java
@@ -0,0 +1,24 @@
+package com.baeldung.spring.stateMachine;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.statemachine.StateMachine;
+
+import com.baeldung.spring.stateMachine.config.JunctionStateMachineConfiguration;
+
+public class JunctionStateMachineTest {
+
+ @Test
+ public void whenTransitioningToJunction_thenArriveAtSubJunctionNode() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JunctionStateMachineConfiguration.class);
+ StateMachine stateMachine = ctx.getBean(StateMachine.class);
+ stateMachine.start();
+
+ stateMachine.sendEvent("E1");
+ Assert.assertEquals("low", stateMachine.getState().getId());
+
+ stateMachine.sendEvent("end");
+ Assert.assertEquals("SF", stateMachine.getState().getId());
+ }
+}
diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java
new file mode 100644
index 0000000000..1fd7bd85f0
--- /dev/null
+++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateEnumMachineTest.java
@@ -0,0 +1,33 @@
+package com.baeldung.spring.stateMachine;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.statemachine.StateMachine;
+
+import com.baeldung.spring.stateMachine.applicationReview.ApplicationReviewEvents;
+import com.baeldung.spring.stateMachine.applicationReview.ApplicationReviewStates;
+import com.baeldung.spring.stateMachine.config.SimpleEnumStateMachineConfiguration;
+
+public class StateEnumMachineTest {
+
+ private StateMachine stateMachine;
+
+ @Before
+ public void setUp() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SimpleEnumStateMachineConfiguration.class);
+ stateMachine = ctx.getBean(StateMachine.class);
+ stateMachine.start();
+ }
+
+ @Test
+ public void whenStateMachineConfiguredWithEnums_thenStateMachineAcceptsEnumEvents() {
+ assertTrue(stateMachine.sendEvent(ApplicationReviewEvents.APPROVE));
+ assertEquals(ApplicationReviewStates.PRINCIPAL_REVIEW, stateMachine.getState().getId());
+ assertTrue(stateMachine.sendEvent(ApplicationReviewEvents.REJECT));
+ assertEquals(ApplicationReviewStates.REJECTED, stateMachine.getState().getId());
+ }
+}
diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineBuilderTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineBuilderTest.java
new file mode 100644
index 0000000000..cdd1e951e0
--- /dev/null
+++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineBuilderTest.java
@@ -0,0 +1,35 @@
+package com.baeldung.spring.stateMachine;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+import org.springframework.statemachine.StateMachine;
+import org.springframework.statemachine.config.StateMachineBuilder;
+
+public class StateMachineBuilderTest {
+
+ @Test
+ public void whenUseStateMachineBuilder_thenBuildSuccessAndMachineWorks() throws Exception {
+ StateMachineBuilder.Builder builder = StateMachineBuilder.builder();
+ builder.configureStates().withStates()
+ .initial("SI")
+ .state("S1")
+ .end("SF");
+
+ builder.configureTransitions()
+ .withExternal()
+ .source("SI").target("S1").event("E1")
+ .and().withExternal()
+ .source("S1").target("SF").event("E2");
+
+ StateMachine machine = builder.build();
+
+ machine.start();
+
+ machine.sendEvent("E1");
+ assertEquals("S1", machine.getState().getId());
+
+ machine.sendEvent("E2");
+ assertEquals("SF", machine.getState().getId());
+ }
+}
diff --git a/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineTest.java b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineTest.java
new file mode 100644
index 0000000000..1b442bf994
--- /dev/null
+++ b/spring-state-machine/src/test/java/com/baeldung/spring/statemachine/StateMachineTest.java
@@ -0,0 +1,47 @@
+package com.baeldung.spring.stateMachine;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.statemachine.StateMachine;
+
+import com.baeldung.spring.stateMachine.config.SimpleStateMachineConfiguration;
+
+public class StateMachineTest {
+
+ private StateMachine stateMachine;
+
+ @Before
+ public void setUp() {
+ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SimpleStateMachineConfiguration.class);
+ stateMachine = ctx.getBean(StateMachine.class);
+ stateMachine.start();
+ }
+
+ @Test
+ public void whenSimpleStringStateMachineEvents_thenEndState() {
+ assertEquals("SI", stateMachine.getState().getId());
+
+ stateMachine.sendEvent("E1");
+ assertEquals("S1", stateMachine.getState().getId());
+
+ stateMachine.sendEvent("E2");
+ assertEquals("S2", stateMachine.getState().getId());
+
+ stateMachine.sendEvent("end");
+ assertEquals("SF", stateMachine.getState().getId());
+
+ }
+
+ @Test
+ public void whenSimpleStringMachineActionState_thenActionExecuted() {
+ stateMachine.sendEvent("E3");
+ assertEquals("S3", stateMachine.getState().getId());
+
+ stateMachine.sendEvent("E4");
+ assertEquals("S4", stateMachine.getState().getId());
+ assertEquals(2, stateMachine.getExtendedState().getVariables().get("approvalCount"));
+ }
+}