PlantUML state diagrams: four ways to model an order lifecycle
State diagrams are the most underused PlantUML type. They precisely describe all legal states of a domain object (order, login session, approval flow), transitions, guards, and entry actions.
What state diagrams solve
Order lifecycle looks like this in practice:
- pending → paid → shipping → delivered → completed
- paid → refund-requested → refunded
- pending → cancelled
- pending → timeout
- delivered → after-sale-requested → after-sale → after-sale-done
In code these become a sea of if/else/switch. State diagrams draw the lifecycle first; the code mirrors the diagram.
1. The most basic state diagram
1 | @startuml |
[*]is the start/end pseudo-stateStateA --> StateB : trigger eventis the transition- Renders as rounded rectangles + black arrows + grey default fill
This covers 80% of domain objects.
2. Add guard conditions
Not every transition should fire. Use [] after the trigger:
1 | @startuml |
[condition] is the standard UML guard syntax.
3. Add entry actions and activities
What should run when entering a state (send email, reserve stock):
1 | @startuml |
Syntax: src --> dst : trigger / entry action. The slash + spaces matter.
4. Composite / nested states
“Shipping” is a process, not a single step. Nest:
1 | @startuml |
state X { ... } declares X’s sub-state machine. PlantUML renders a big rounded rectangle containing smaller ones.
5. History markers (H / H*)
When leaving and re-entering a composite state, do you return to the entry or to the last visited sub-state?
1 | @startuml |
PlantUML has limited support for H / H*; for serious history tracking, store the current sub-state in your data and re-enter with that target.
6. Concurrent regions (fork / join)
Multiple parallel things within one state:
1 | @startuml |
PlantUML supports fork / join via <<fork>> / <<join>> stereotypes but the simpler way is just two outgoing arrows from the same state:
1 | PendingPayment --> RiskCheck : parallel -1 |
The reviewer reads parallel intent fine.
7. Time-triggered transitions
PlantUML doesn’t natively support after 24h. Workarounds:
1 | @startuml |
<&hourglass> marks “triggered by an external scheduler.” Code side:
1 | // The diagram describes WHAT should happen; |
Real example: login session
1 | @startuml |
This drives the code:
1 | switch (session.state) { |
Review checklist
- All start/end pseudo-states use
[*]? - Guards (
[...]) are after the trigger label? - Entry actions (
/ action) defined and impl’d? - Composite state sub-diagram is independently readable?
- Time-triggered transitions reference a cron / scheduler in code?
- State graph covers all legal paths and exception paths?
Pitfalls
- Long labels:
Pending --> Timeout : user clicked cancel and gateway callback failed and the order is unconfirmed— PlantUML truncates. Move the detail to anote. [*]as transition source: you must write[*] --> X : eventexplicitly.- Composite
[*] -->everywhere: nested sub-state entry uses the parent state entry; don’t sprinkle[*] -->. - No brackets around guards: PlantUML treats the bare label as a trigger description, badly.
- Time triggers ignored:
after 24hdoesn’t work in PlantUML; either use icons for a scheduler or move into code cron.
Comparison with UML standard
| Feature | UML Standard | PlantUML |
|---|---|---|
| Simple state | ✅ | ✅ |
| Start / terminate | ✅ | ✅ |
| Trigger | ✅ | ✅ |
| Guard | ✅ | ✅ |
| Entry / exit actions | ✅ | ✅ (entry / exit) |
| Composite | ✅ | ✅ |
| History (H / H*) | ✅ | ⚠️ Partial |
| Deep history | ✅ | ⚠️ Partial |
| Fork / join | ✅ | ✅ |
Time trigger after |
✅ | ❌ (icon + external cron) |
PlantUML state diagrams load into UML tools (EA, StarUML) without translation.
TL;DR
State diagrams are PlantUML’s most underused type. Draw the lifecycle first, then code mirrors the diagram — no more spaghetti if/else.
- Title: PlantUML state diagrams: four ways to model an order lifecycle
- Author: puml.online
- Created at : 2026-07-29 15:00:00
- Updated at : 2026-08-14 21:34:29
- Link: https://puml.online/blog/plantuml-state-diagram-en/
- License: This work is licensed under CC BY-NC-SA 4.0.