Spring StateMachine: From Finite State Machines to Production-Ready Payment Flows
A finite state machine (FSM) is a general computer-science model used to represent systems that move between a fixed set of states.
Spring Statemachine brings that idea into the Spring ecosystem and gives us a way to model those state transitions in Java applications.
A payment lifecycle is a good example.
A transaction might start like this:
CREATED
↓
PENDING
↓
AUTHORIZED
↓
COMPLETEDThere are other possible paths too:
PENDING
↓
FAILEDor:
PENDING
↓
CANCELLEDThe key idea is that a transaction cannot move from one state to another arbitrarily.
For example, allowing:
COMPLETED → PENDINGwould probably violate the business rules of the payment system.
An FSM gives us a structured way to define which movements are allowed and which aren’t.
1. What is a Finite State Machine?
An FSM models a system using a finite number of states and predefined transitions between them.
A typical FSM has four core concepts:
- State
- Event
- Transition
- Action
Let’s see what each one means in a payment system.
State
A state describes where the transaction currently is in its lifecycle.
For example:
CREATED
PENDING
AUTHORIZED
COMPLETED
FAILED
CANCELLEDEvent
An event represents something that happened or something that was requested.
Examples:
SUBMIT
AUTHORIZE
AUTH_SUCCESS
AUTH_FAILED
COMPLETE
CANCELTransition
A transition defines how an event moves an entity from one state to another.
For example:
PENDING + AUTH_SUCCESS
↓
AUTHORIZEDAction
An action is work that needs to happen when a transition occurs.
For example:
PENDING
|
| AUTH_SUCCESS
↓
AUTHORIZED
|
+--> update database
+--> publish event
+--> write audit record2. The mental model
A simple way to think about an FSM is:
Current State
+
Event
↓
State Machine
↓
Next State
+
ActionFor example:
PENDING + AUTH_SUCCESS
↓
AUTHORIZEDThe state machine looks at the current state and the incoming event, checks whether that combination has a valid transition, and then moves the entity to the next state.
3. States and events are not the same thing
This is one of the first distinctions you should get comfortable with.
Consider:
AUTHORIZEDThis describes the current condition of a payment, so it is a state.
Now consider:
AUTH_SUCCESSThis describes something that happened, so it is an event.
A useful way to remember it is:
State = Where am I?Event = What just happened?For example:
AUTHORIZING
+
AUTH_SUCCESS
↓
AUTHORIZED4. Why not just use if/else?
For a small application, there is nothing inherently wrong with using conditions.
You could write:
if (status.equals("PENDING")) {if (event.equals("AUTH_SUCCESS")) {
status = "AUTHORIZED";
} else if (event.equals("CANCEL")) {
status = "CANCELLED";
}
}
else if (status.equals("AUTHORIZED")) { if (event.equals("COMPLETE")) {
status = "COMPLETED";
}
}
This works.
The problem starts when the lifecycle becomes more complicated.
Imagine a payment system with 20 states and dozens of possible events.
Eventually you can end up with deeply nested conditions spread across multiple services:
if
if
if
else if
if
elseAt that point, answering a simple question such as:
“Can a payment in AUTHORIZING be cancelled?”
may require searching through several classes.
With an FSM, the business rules can be represented directly:
PENDING + AUTH_SUCCESS → AUTHORIZED
PENDING + CANCEL → CANCELLEDAUTHORIZED + START_PROCESSING → PROCESSINGPROCESSING + PROCESS_SUCCESS → COMPLETED
PROCESSING + PROCESS_FAILED → FAILED
The state model becomes much easier to inspect and test.
5. Designing a payment state machine
Let’s build a slightly more realistic payment lifecycle.
States
public enum PaymentState { CREATED,
PENDING,
AUTHORIZING,
AUTHORIZED,
PROCESSING,
COMPLETED,
FAILED,
CANCELLED
}Events
public enum PaymentEvent { SUBMIT,
START_AUTHORIZATION,
AUTH_SUCCESS,
AUTH_FAILED,
START_PROCESSING,
PROCESS_SUCCESS,
PROCESS_FAILED,
CANCEL
}Now we can define the lifecycle.
CREATED
|
| SUBMIT
↓
PENDING
|
| START_AUTHORIZATION
↓
AUTHORIZING
|
+---- AUTH_SUCCESS ----> AUTHORIZED
|
+---- AUTH_FAILED -----> FAILEDPENDING
|
| CANCEL
↓
CANCELLEDAUTHORIZED
|
| START_PROCESSING
↓
PROCESSING
|
+---- PROCESS_SUCCESS --> COMPLETED
|
+---- PROCESS_FAILED ---> FAILED
Even without writing Java code, this diagram already describes a large part of the payment lifecycle.
6. Bringing Spring StateMachine into the picture
Spring Statemachine provides infrastructure for modeling these concepts in a Spring application.
Depending on the version you’re using, the API and configuration style can differ, so it’s worth checking the documentation for the specific Spring Statemachine release in your project.
The concepts themselves remain the same:
States
Events
Transitions
Actions
Guards
Listeners
PersistenceA typical project structure might look like this:
src/main/java
|
+-- state
| |
| +-- PaymentState.java
| +-- PaymentEvent.java
| +-- PaymentStateConfig.java
| +-- PaymentActions.java
| +-- PaymentGuards.java
|
+-- service
| |
| +-- PaymentService.java
|
+-- controller
|
+-- PaymentController.java7. Defining the states
Our state enum is straightforward:
public enum PaymentState { CREATED,
PENDING,
AUTHORIZING,
AUTHORIZED,
PROCESSING,
COMPLETED,
FAILED,
CANCELLED
}These represent the finite states through which a payment can move.
8. Defining events
The events are separate:
public enum PaymentEvent { SUBMIT,
START_AUTHORIZATION,
AUTH_SUCCESS,
AUTH_FAILED,
START_PROCESSING,
PROCESS_SUCCESS,
PROCESS_FAILED,
CANCEL
}For example:
CREATED + SUBMIT
↓
PENDINGThe event is SUBMIT.
The resulting state is PENDING.
Keeping this distinction clear makes the rest of the design much easier.
9. Defining transitions
Our transition rules look like this:
CREATED --SUBMIT--> PENDINGPENDING --START_AUTHORIZATION--> AUTHORIZINGAUTHORIZING --AUTH_SUCCESS--> AUTHORIZEDAUTHORIZING --AUTH_FAILED--> FAILEDPENDING --CANCEL--> CANCELLEDAUTHORIZED --START_PROCESSING--> PROCESSINGPROCESSING --PROCESS_SUCCESS--> COMPLETEDPROCESSING --PROCESS_FAILED--> FAILED
In a Spring Statemachine configuration, the transitions are expressed using the framework’s configuration API.
Conceptually, a transition looks like:
.withExternal()
.source(PaymentState.CREATED)
.target(PaymentState.PENDING)
.event(PaymentEvent.SUBMIT);Another transition would look like:
.withExternal()
.source(PaymentState.PENDING)
.target(PaymentState.AUTHORIZING)
.event(PaymentEvent.START_AUTHORIZATION);The precise configuration APIs can vary between Spring Statemachine versions, so don’t copy configuration code from an old tutorial without checking the version used by your application.
10. External and internal transitions
You may come across the terms external transition and internal transition.
An external transition changes the current state.
For example:
PENDING
↓
AUTHORIZEDAn internal transition handles an event without changing the state.
For example:
PROCESSING
|
| HEARTBEAT
↓
PROCESSINGThe payment remains in PROCESSING, but the event could trigger an action such as recording a heartbeat or updating monitoring information.
11. Actions
An action is work that runs when a transition occurs.
Suppose this happens:
AUTHORIZING
|
| AUTH_SUCCESS
↓
AUTHORIZEDAfter authorization succeeds, the application might need to:
- save authorization details
- write an audit record
- publish a Kafka event
- update metrics
Conceptually, an action can access information associated with the state machine:
Action<PaymentState, PaymentEvent> authorizationSuccess =
context -> {Payment payment =
context.getExtendedState()
.get("payment", Payment.class); System.out.println(
"Payment authorized: " + payment.getId()
);
};
The actual implementation should be designed carefully around your transaction boundaries and persistence strategy.
12. Keep business side effects separate
It is tempting to put everything inside a transition configuration.
For example:
Transition
|
+--> update DB
+--> call payment gateway
+--> publish Kafka event
+--> send email
+--> write auditThat quickly becomes difficult to maintain.
A cleaner structure is:
Transition
↓
Action
↓
Business Service
|
+--> persistence
+--> messaging
+--> auditThe state machine describes the lifecycle. Your application services handle the actual business operations.
13. Guards
Sometimes an event should only be accepted when a particular condition is satisfied.
Suppose a payment is pending and we receive an authorization request.
Authorization should only proceed when:
payment amount <= available limitA guard can represent that condition.
Conceptually:
amount <= limit
|
YES
↓
PENDING ----------> AUTHORIZEDIf the condition isn’t satisfied, the transition isn’t taken.
A guard therefore answers:
“Is this transition allowed right now?”
14. Guard example
Conceptually:
Guard<PaymentState, PaymentEvent> amountGuard =
context -> {Payment payment =
context.getExtendedState()
.get("payment", Payment.class); return payment.getAmount()
.compareTo(payment.getAvailableLimit()) <= 0;
};
The flow becomes:
Event
↓
Guard
|
+---- false → transition not taken
|
+---- true → transition happens15. Guard vs Action
This distinction is worth remembering.
Guard
Determines whether a transition is allowed.
Can this happen?Action
Runs work associated with the transition.
What should happen when it does?So the flow is:
Event
↓
Guard
↓
Allowed?
|
+---- NO
|
+---- YES
↓
Transition
↓
Next State
↓
Action16. Extended State
The current state isn’t always enough to represent everything the application needs to know.
Suppose the payment is currently:
AUTHORIZINGYou might also need:
paymentId = TX123
attemptCount = 2
riskScore = 40These aren’t separate states. They’re contextual information.
Spring Statemachine provides an extended-state mechanism for storing additional variables associated with the state machine.
Conceptually:
State Machine
|
+-- Current State
| |
| +-- AUTHORIZING
|
+-- Extended State
|
+-- paymentId
+-- attemptCount
+-- riskScore17. Don’t create states for every piece of data
This is a common modeling mistake.
You wouldn’t normally want:
AUTHORIZING_ATTEMPT_1
AUTHORIZING_ATTEMPT_2
AUTHORIZING_ATTEMPT_3Instead:
AUTHORIZINGand:
attemptCount = 3The state describes the business lifecycle.
The additional data describes the context around that lifecycle.
18. State machine lifecycle
A state machine also has its own lifecycle.
A simplified view is:
Created
↓
Started
↓
Receive Events
↓
Execute Transitions
↓
StoppedOnce started, an event can be sent to the machine.
For example:
stateMachine.start();Then:
stateMachine.sendEvent(PaymentEvent.SUBMIT);The machine evaluates the current state and the event and determines whether a valid transition exists.
19. Walking through a complete payment
Suppose the initial state is:
CREATEDWe send:
SUBMITThe machine finds:
CREATED + SUBMIT
↓
PENDINGNext:
START_AUTHORIZATIONResult:
PENDING + START_AUTHORIZATION
↓
AUTHORIZINGThen:
AUTH_SUCCESSResult:
AUTHORIZING + AUTH_SUCCESS
↓
AUTHORIZEDNext:
START_PROCESSINGResult:
AUTHORIZED + START_PROCESSING
↓
PROCESSINGFinally:
PROCESS_SUCCESSResult:
PROCESSING + PROCESS_SUCCESS
↓
COMPLETEDThe entire lifecycle becomes:
CREATED
↓ SUBMIT
PENDING
↓ START_AUTHORIZATION
AUTHORIZING
↓ AUTH_SUCCESS
AUTHORIZED
↓ START_PROCESSING
PROCESSING
↓ PROCESS_SUCCESS
COMPLETED20. What happens when an invalid event arrives?
Suppose the current state is:
CREATEDand somebody sends:
PROCESS_SUCCESSThere is no transition defined for:
CREATED + PROCESS_SUCCESSThe state machine therefore doesn’t simply decide to execute arbitrary business logic.
The event isn’t a valid transition from the current state.
That’s one of the major advantages of explicitly modeling the lifecycle.
21. Why this works well for payments
Payments naturally have a lifecycle with business constraints.
For example:
CREATED
↓
PENDING
↓
AUTHORIZED
↓
PROCESSING
↓
COMPLETEDBut certain movements shouldn’t normally be possible:
COMPLETED → CREATED
COMPLETED → PENDING
CANCELLED → PROCESSINGThe state machine gives you a place to define those rules instead of leaving them scattered throughout the application.
22. FSM and the database status field
You might be wondering:
“If I already have payment.status in PostgreSQL, why do I need a state machine?”
The answer is that these solve different problems.
Your database can store:
payment
--------------------------------
id
amount
status
created_at
updated_atFor example:
id status
TX100 AUTHORIZEDThat’s useful because the payment state needs to survive application restarts.
The state machine, meanwhile, defines what transitions are legal.
Without a centralized model, you might have code like this spread throughout the application:
if (status == PENDING) {
...
}if (status == AUTHORIZED) {
...
}if (status == FAILED) {
...
}
The database stores the state.
The state machine manages the lifecycle rules around that state.
23. Persistence becomes important in production
Consider this scenario:
PENDING
↓
AUTH_SUCCESS
↓
AUTHORIZEDThe application then crashes.
When the service starts again, it needs to know that the payment was already authorized.
That’s why the business state should normally be persisted.
For example:
Database
|
| status = AUTHORIZED
↓
State Machine
|
↓
AUTHORIZEDFor more complex state-machine data, Spring Statemachine also provides persistence-related mechanisms.
But there is an architectural distinction worth keeping in mind:
state-machine persistence is not automatically the same thing as your business database.
For a payment system, the payment database should remain an intentional part of the source-of-truth design.
24. State transitions and database transactions
Consider this transition:
PENDING
↓
AUTH_SUCCESS
↓
AUTHORIZEDMaybe the application needs to:
- update the payment
- save authorization details
- create an audit record
- publish a domain event
You need to decide where the transaction boundary belongs.
For example:
Database Transaction
|
+-- update payment
+-- save authorization
+-- write audit
|
COMMITPublishing to Kafka is a separate concern.
Get Manish Pandey’s stories in your inbox
Join Medium for free to get updates from this writer.
A robust architecture may use an outbox pattern so the database change and event publication can be coordinated reliably.
You should not assume that:
State-machine transition
+
Database update
+
Kafka publishautomatically form one atomic transaction.
They don’t.
25. State machine with Kafka or RabbitMQ
A common architecture looks like this:
REST API
↓
Payment Service
↓
State Machine
↓
State Transition
↓
Domain Event
↓
Kafka / RabbitMQFor example:
AUTH_SUCCESS
↓
State Machine
↓
AUTHORIZED
↓
PaymentAuthorized
↓
KafkaOther services can consume the domain event:
Kafka
|
+-----------+-----------+
| | |
↓ ↓ ↓
Fraud Audit NotificationThis fits naturally with event-driven payment architectures.
26. State-machine events vs domain events
These two concepts are easy to mix up.
State-machine event
AUTH_SUCCESSThis is input to the state machine.
It tells the machine:
“Authorization succeeded.”
Domain event
PaymentAuthorizedThis represents something meaningful that happened in the business domain.
A typical flow can therefore be:
AUTH_SUCCESS
↓
State Machine
↓
AUTHORIZED
↓
PaymentAuthorized
↓
KafkaThe first event drives the state transition.
The second event informs other parts of the system about the resulting business change.
27. State transition tables
Before writing the Java configuration, create a transition table.
For example:
Current State
Event
Next State
CREATED
SUBMIT
PENDING
PENDING
START_AUTHORIZATION
AUTHORIZING
PENDING
CANCEL
CANCELLED
AUTHORIZING
AUTH_SUCCESS
AUTHORIZED
AUTHORIZING
AUTH_FAILED
FAILED
AUTHORIZED
START_PROCESSING
PROCESSING
PROCESSING
PROCESS_SUCCESS
COMPLETED
PROCESSING
PROCESS_FAILED
FAILED
This table is more than documentation.
It is the business model you’re implementing.
Once the table is correct, writing the Java configuration becomes much easier.
28. Testing the state machine
Each transition should have a test.
For example:
@Test
void shouldMoveCreatedToPending() {// Given
// state machine starts in CREATED // When
machine.sendEvent(PaymentEvent.SUBMIT); // Then
assertEquals(
PaymentState.PENDING,
machine.getState().getId()
);
}
You should also test invalid transitions.
For example:
CREATED + PROCESS_SUCCESSshould not produce:
COMPLETEDLikewise:
PENDING + PROCESS_SUCCESSshould not unexpectedly move the payment forward.
Testing illegal transitions is just as important as testing successful ones.
29. Conditional transitions
Now imagine that authorization can have different outcomes.
AUTHORIZING
|
decision
/ \
/ \
approved risk
↓ ↓
AUTHORIZED REVIEWThe decision can be represented through guards or other state-machine mechanisms.
For example:
riskScore < threshold
↓
AUTHORIZEDOtherwise:
riskScore >= threshold
↓
REVIEWThis is where an FSM starts becoming much more useful than a collection of status strings.
30. Hierarchical states
Large state machines can become difficult to manage if every detail is represented at the same level.
Imagine:
PAYMENT_PROCESSINGcontains different processing flows:
PAYMENT_PROCESSING
|
+-- CARD
| |
| +-- VALIDATING
| +-- AUTHORIZING
|
+-- UPI
| |
| +-- VALIDATING
| +-- AUTHORIZING
|
+-- BANK
|
+-- VALIDATING
+-- PROCESSINGThis is the idea behind hierarchical state machines.
Instead of putting every possible state into one huge flat graph, related states can be grouped together.
31. FSM vs workflow engine
These concepts overlap, but they solve somewhat different problems.
A state machine is naturally suited to an entity lifecycle:
Payment
↓
CREATED
↓
AUTHORIZED
↓
COMPLETEDA workflow is often concerned with coordinating a longer sequence of activities:
Receive payment
↓
Fraud check
↓
Manual approval
↓
Settlement
↓
Notification
↓
ReconciliationA useful distinction is:
FSM
→ What state is this entity in?
→ Which transitions are allowed?Workflow
→ Which business activities need to happen?
→ In what order?A real system can use both.
32. FSM vs Spring Batch
Spring Batch and a state machine solve different problems.
Spring Batch is designed for processing large amounts of data reliably.
For example:
Settlement Batch
↓
Read 1 million payments
↓
Process payments
↓
Write resultsA state machine manages lifecycle transitions:
PENDING
↓
AUTHORIZED
↓
COMPLETEDThey can still work together.
For example:
Settlement Batch
↓
Read payments
↓
For each payment
↓
State Machine
↓
Validate transition
↓
Update state33. FSM vs Kafka
Kafka is a messaging platform.
A state machine is a business-state model.
For example:
Kafka
|
| AUTH_SUCCESS
↓
Payment Service
|
↓
State Machine
|
↓
AUTHORIZEDKafka transports the event.
The state machine determines whether that event is valid for the payment’s current state and what should happen next.
They complement each other rather than replacing each other.
34. Be careful with ownership
A dangerous architecture is one where multiple services can independently modify the same payment lifecycle.
For example:
Kafka
↓
Service A
↓
State MachineKafka
↓
Service B
↓
State MachineKafka
↓
Service C
↓
State Machine
Who actually owns the payment state?
That question should have a clear answer.
A cleaner model might be:
Payment Service
|
+-- owns Payment state
|
+-- owns state machine
|
+-- publishes domain eventsOther services consume those events without directly changing the payment’s lifecycle.
This becomes particularly important once multiple services and asynchronous messaging are involved.
35. Retry and recovery states
Real payment systems have transient failures.
A simple model might be:
AUTHORIZING
|
| failure
↓
FAILEDBut a timeout doesn’t always mean the payment has permanently failed.
You might instead introduce:
AUTHORIZING
|
| timeout
↓
RETRY_PENDING
|
↓
AUTHORIZINGYou could keep:
attemptCount = 1
attemptCount = 2
attemptCount = 3in persistent state or context.
After the maximum number of attempts:
AUTHORIZING
↓
RETRY_PENDING
↓
maximum retries reached
↓
FAILEDThis is closer to what you’d encounter in a production payment flow.
36. Terminal states
Some states should have no normal outgoing transitions.
For example:
COMPLETED
CANCELLEDOnce a payment is completed, you probably don’t want it silently moving back into:
PENDINGA terminal state can therefore be modeled without outgoing transitions:
COMPLETED
|
+--- no outgoing transitionsWhether FAILED is terminal depends on the business design. Some systems allow a failed payment to be retried, while others create a new payment attempt instead.
37. State-machine invariants
A useful way to design the model is to define what each state guarantees.
For example:
COMPLETED
Could mean:
Payment processing succeeded.Therefore:
COMPLETED → PENDINGwould violate that meaning.
CANCELLED
Could mean:
Payment will no longer be processed.Therefore:
CANCELLED → PROCESSINGwould also violate the business rule.
These are invariants.
The state machine becomes one mechanism for enforcing those lifecycle rules.
38. Concurrency: a senior-level concern
Now we get to one of the most important production problems.
Suppose:
Current state = PENDINGTwo events arrive almost simultaneously:
AUTH_SUCCESS
CANCELYou could potentially have:
+--> AUTHORIZED
/
PENDING ----
\
+--> CANCELLEDWhich one should win?
A state machine library alone doesn’t magically solve distributed concurrency.
Your application needs a strategy.
Possible approaches include:
- serializing events for a payment
- optimistic locking
- database version columns
- partitioning Kafka messages by payment ID
- controlled synchronization
- single-threaded processing per aggregate
- database-level constraints
For distributed payment systems, this question can be more important than the choice of state-machine framework.
39. Optimistic locking
Consider a payment table:
CREATE TABLE payment (
id BIGINT PRIMARY KEY,
status VARCHAR(30),
version BIGINT
);Suppose:
TX100
status = PENDING
version = 5Two threads read it.
Both see:
version = 5Thread A processes:
AUTH_SUCCESSand updates:
PENDING → AUTHORIZED
version 5 → 6Thread B then tries:
PENDING → CANCELLEDbut it is still working with version 5.
The database can reject the stale update because the version is no longer 5.
This gives you protection against concurrent updates.
40. A state machine isn’t a replacement for database consistency
It’s possible for your in-memory state machine to say:
AUTHORIZEDwhile another piece of code changes the database to:
CANCELLEDunless the architecture prevents that.
That’s why you need clear ownership and persistence rules.
The state machine should be part of the consistency design, not the entire consistency mechanism.
41. How to design an FSM from scratch
When you’re given a new business problem, resist the temptation to start writing Java immediately.
Start with the domain.
Step 1 — Identify the entity
For our example:
PaymentStep 2 — Identify meaningful business states
CREATED
PENDING
AUTHORIZED
PROCESSING
COMPLETED
FAILED
CANCELLEDStep 3 — Identify events
SUBMIT
AUTHORIZE
AUTH_SUCCESS
AUTH_FAILED
CANCEL
PROCESS
COMPLETEStep 4 — Build the transition table
Current State | Event | Next StateStep 5 — Identify guards
Ask:
What conditions must be true?Step 6 — Identify actions
Ask:
What side effects happen?Step 7 — Identify terminal states
For example:
COMPLETED
CANCELLEDStep 8 — Define recovery paths
Think about:
timeout
retry
manual review
compensationStep 9 — Define persistence
Decide:
Where is the authoritative state stored?Step 10 — Define concurrency
Ask:
What happens if two events arrive at almost the same time?Only after answering these questions should you move into the framework configuration.
42. A practical learning path
If you’re learning Spring Statemachine from scratch, I’d go in this order:
Level 1
FSM fundamentals
↓
Level 2
States and Events
↓
Level 3
Transitions
↓
Level 4
Actions
↓
Level 5
Guards
↓
Level 6
Extended State
↓
Level 7
Listeners
↓
Level 8
Conditional transitions
↓
Level 9
Hierarchical states
↓
Level 10
Persistence
↓
Level 11
Recovery and restart
↓
Level 12
Concurrency
↓
Level 13
Kafka/RabbitMQ integration
↓
Level 14
Outbox and transaction boundaries
↓
Level 15
Production architectureAt that point, you’re no longer just learning an API. You’re learning how to model business lifecycles.
43. Build a real project
Rather than starting with something like a traffic-light example, build a payment lifecycle state machine.
Start with:
CREATED
↓
PENDING
↓
AUTHORIZING
↓
AUTHORIZED
↓
PROCESSING
↓
COMPLETEDThen introduce failure paths:
AUTHORIZING → FAILED
PROCESSING → FAILED
PENDING → CANCELLEDAfter that, add:
- PostgreSQL
- Spring Data JPA
- REST APIs
- Kafka or RabbitMQ
- Optimistic locking
- Retry handling
- Audit records
- Outbox pattern
- Metrics
- Transition tests
Once you add those pieces, you’ll start seeing how an FSM fits into a real Spring Boot application rather than existing as an isolated framework exercise.
44. The concepts you should remember
At the end of the day, these terms should be very clear.
State
Where is the entity right now?
AUTHORIZEDEvent
What happened?
AUTH_SUCCESSTransition
How can the entity move?
AUTHORIZING + AUTH_SUCCESS
↓
AUTHORIZEDGuard
Is the transition allowed?
amount <= availableLimitAction
What work should happen when the transition occurs?
save authorization
publish event
write auditExtended State
What additional context does the machine need?
attemptCount
riskScore
paymentIdPersistence
How does the state survive an application restart?
PostgreSQLListener
How can other components observe state-machine activity?
state changed
transition completed
event rejectedAnd the whole idea can be reduced to one flow:
Current State
+
Event
|
↓
Guard
|
+------+------+
| |
false true
| |
No transition Transition
|
↓
Next State
|
↓
ActionThat’s the foundation.
Once this mental model is solid, Spring Statemachine becomes much easier to understand because you’re no longer learning a collection of framework methods. You’re mapping a well-defined business model onto Java.





