Loading content...
Every substantial object in a software system has a lifecycle—a sequence of states it passes through from creation to destruction, responding to events along the way. An Order transitions from Draft to Submitted to Approved to Shipped to Delivered. A Connection moves from Closed to Opening to Open to Closing. A User Account progresses from Pending to Active to Suspended to Deleted.
Understanding these lifecycles isn't optional—it's essential. Bugs in state management cause some of the most insidious defects: orders that can be paid twice, connections that hang forever, accounts that become inconsistent. State Diagrams (formally called State Machine Diagrams in UML) provide the rigorous vocabulary to model, validate, and communicate object behavior over time.
By the end of this page, you will master State Machine Diagrams—their notation, semantics, and advanced features. You'll understand states, transitions, events, guards, and actions. You'll learn to model composite states, concurrent regions, and entry/exit behaviors. Most importantly, you'll be able to design robust object lifecycles that prevent invalid state transitions.
A State Machine Diagram (often simply called a State Diagram) models the discrete states an object can be in and the transitions between those states triggered by events. It answers fundamental questions:
Historical Context:
State machines have deep roots in computer science:
UML State Machine Diagrams inherit the power of Statecharts—particularly hierarchical (composite) states and concurrent regions—while providing standardized notation understood across the industry.
Both model dynamic behavior, but from different perspectives. Activity diagrams model the flow of control through a process—what activities happen in what order. State diagrams model the lifecycle of a single object—what states it can be in and how it responds to events. Use activity diagrams for workflows; use state diagrams for object behavior.
State diagrams use a precise set of symbols with well-defined semantics. Understanding this vocabulary is essential for creating accurate models.
States:
A state represents a condition or situation during the life of an object in which it satisfies some condition, performs some activity, or waits for some event. States are drawn as rounded rectangles with the state name inside.
┌─────────────────────┐
│ Active │
└─────────────────────┘
State characteristics:
Pseudo-States:
Pseudo-states are special notation elements that are not true states but control transition flow:
| Symbol | Name | Purpose |
|---|---|---|
| ▢ (rounded) | State | A condition during object lifecycle |
| ● | Initial Pseudo-state | Entry point to the state machine |
| ◉ | Final State | Termination of the state machine |
| → | Transition | Change from one state to another |
| ◇ | Choice Pseudo-state | Dynamic branching based on guard evaluation |
| H / H* | History Pseudo-state | Returns to last active substate (shallow/deep) |
| ⊙ | Entry/Exit Point | Named entry or exit from composite state |
Transitions:
A transition represents a relationship between two states indicating that when a specified event occurs and certain conditions are met, the object moves from the first state to the second. Transitions are drawn as arrows with labels.
Transition label syntax:
event [guard] / action
Example transitions:
submit [isValid] / sendConfirmation()
approve / notifyCustomer()
timeout
cancel [canCancel()]
Each component is optional:
clicksubmit [isValid]approve / logApproval()payment [amount > 0] / processPayment()A transition without an event trigger is called a 'completion transition' or 'triggerless transition'. It fires automatically when the source state completes all its internal activities. This is how states can flow naturally from one to the next without explicit external events.
Events are the stimuli that trigger state transitions. Understanding event types is crucial for accurate state modeling.
Call Events:
The most common event type—a request to execute an operation on the object:
approve() — Someone calls the approve operation
cancel() — Someone calls the cancel operation
process(order) — Called with parameter
Call events typically correspond to method invocations in your implementation.
Signal Events:
Asynchronous messages sent to the object (as opposed to synchronous calls):
PaymentReceived
TimeoutExpired
UserLoggedOut
Signals are named communications that don't require immediate response. They're queued if the object is busy.
Time Events:
Events based on time passage:
after(30 days) — 30 days after entering the state
after(5 minutes) — 5 minutes after entering the state
at(midnight) — At a specific time
Time events are essential for modeling timeouts, expiration, and scheduled behaviors.
submit(), cancel(reason)PaymentConfirmed, StockDepletedafter(30 min) or absolute: at(2024-12-31)when(balance < 0), when(stockLevel == 0)Change Events:
Events that fire when a condition becomes true (continuous monitoring):
when(accountBalance < 0)
when(temperature > 100)
when(queue.size > maxCapacity)
Change events are evaluated continuously (conceptually). The transition fires the moment the condition becomes true.
Deferred Events:
Sometimes an event arrives when the object can't handle it immediately. UML supports deferring events:
┌─────────────────────────┐
│ Processing │
│ ─────────────────────── │
│ cancel / defer │
└─────────────────────────┘
Deferred events are queued and reconsidered when the object enters a state that can handle them. This prevents event loss without requiring immediate processing.
Event Parameters:
Events can carry data that influences transitions:
payment(amount, method) [amount > 0] / processPayment(amount, method)
update(newValue) / setValue(newValue)
Parameters are available to guards and actions on the transition.
A common design error is conflating different event types. Call events imply synchronous interaction; signal events imply asynchronous messaging. Time events require timer infrastructure. Change events require condition monitoring. Choose the event type that matches your actual system's communication patterns.
Guards are Boolean expressions that determine whether a transition may fire when its triggering event occurs. They enable conditional behavior within state machines.
Guard Syntax:
Guards are enclosed in square brackets on the transition label:
[guard expression]
Examples:
approve [isManager]
submit [form.isValid()]
withdraw [balance >= amount]
ship [inventory.available(item) && !order.isCancelled()]
Evaluation Semantics:
Pattern: Mutually Exclusive Guards
When the same event can lead to different states based on conditions:
┌─────────────────────────────────┐
│ Submitted │
└─────────────────────────────────┘
│
review event occurs
↓
┌─────────────────────────────────┐
│ ◇ Choice │
└─────────────────────────────────┘
╱ ╲
[isApproved] [isRejected]
↓ ↓
┌─────────┐ ┌─────────┐
│ Approved│ │ Rejected│
└─────────┘ └─────────┘
The choice pseudo-state (◇) makes the branching explicit. Guards must be evaluated after the event but before committing to a target state.
Dynamic vs Static Guards:
Dynamic guards require careful design—the system must handle cases where conditions change between event occurrence and guard evaluation.
UML supports an [else] guard that matches when no other guard is true. Use it sparingly—explicit conditions are clearer than implicit else logic. When you do use [else], ensure it's intentional and not masking forgotten cases.
Actions are behaviors that execute during state machine operation. UML distinguishes several types based on when they execute.
Transition Actions:
Actions specified on a transition execute during the transition—after leaving the source state but before entering the target state:
approve / sendNotification(); updateAuditLog()
Transition actions are atomic (conceptually instantaneous). They have access to:
Entry Actions:
Actions that execute every time a state is entered, regardless of which transition led there:
┌─────────────────────────────────┐
│ Active │
├─────────────────────────────────┤
│ entry / startMonitoring() │
└─────────────────────────────────┘
Entry actions are perfect for initialization: starting timers, acquiring resources, sending notifications. They execute after any transition action.
| Action Type | When It Executes | Notation | Use Case |
|---|---|---|---|
| Transition | During transition | event / action | Logging, notifications, data transformation |
| Entry | On entering state | entry / action | Initialization, resource acquisition, start timers |
| Exit | On leaving state | exit / action | Cleanup, resource release, stop timers |
| Do Activity | While in state | do / activity | Background processing, polling, monitoring |
Exit Actions:
Actions that execute every time a state is exited, regardless of which transition is taken:
┌─────────────────────────────────┐
│ Active │
├─────────────────────────────────┤
│ entry / startMonitoring() │
│ exit / stopMonitoring() │
└─────────────────────────────────┘
Exit actions handle cleanup: stopping timers, releasing resources, finalizing data. They execute before any transition action.
Do Activities:
Unlike instantaneous actions, do activities represent ongoing behavior while in a state:
┌─────────────────────────────────┐
│ Processing │
├─────────────────────────────────┤
│ do / performBackgroundWork() │
└─────────────────────────────────┘
Do activities:
Execution Order:
For a transition from State A to State B with transition action T:
Entry and exit actions are state-centric—they run regardless of which transition is used. Transition actions are transition-specific—they run only for that particular path. Use entry/exit for state-invariant behavior; use transition actions for path-specific behavior.
One of the most powerful features of UML State Machines (inherited from Harel Statecharts) is composite states—states that contain other states. This enables hierarchical modeling and dramatically reduces diagram complexity.
Why Composite States?
Without hierarchy, state machines suffer from state explosion. Consider a system with 5 main states and 3 error-handling states. In a flat state machine, you'd need 5 × 3 = 15 error transitions. With a composite state wrapping the main states, you need just 3 error transitions from the container.
Composite State Notation:
┌─────────────────────────────────────────────────────┐
│ Processing │
├─────────────────────────────────────────────────────┤
│ │
│ ● → [Validating] → [Calculating] → [Saving] │
│ │
│ ↓ │
│ ┌──────────┐ │
│ │ Complete │ │
│ └──────────┘ │
└─────────────────────────────────────────────────────┘
│
│ error
↓
┌─────────┐
│ Error │
└─────────┘
The Processing state contains substates. The error transition from Processing can fire from any substate, exiting the entire composite.
Transitions Into and Out of Composites:
History States:
When re-entering a composite state, you might want to resume where you left off rather than starting over. History pseudo-states enable this:
┌───────────────────────────────────────────────┐
│ Editing │
├───────────────────────────────────────────────┤
│ │
│ ● → [Draft] → [Review] → [Finalize] │
│ │
│ (H) ←── resume transition │
└───────────────────────────────────────────────┘
When transitioning to the history pseudo-state (H), the state machine enters whichever substate was active when it last left the composite.
Submachine States:
For very complex substates, you can reference an entire separate state machine diagram:
┌─────────────────────────────────┐
│ Payment Processing : PaymentSM │
└─────────────────────────────────┘
The : PaymentSM indicates this state's behavior is defined by the PaymentSM state machine.
While UML allows unlimited nesting, practical readability limits hierarchy depth. Two or three levels are usually manageable. Beyond that, consider splitting into separate state machine diagrams connected via submachine references.
Objects sometimes have independent aspects of state that evolve concurrently. UML models this with orthogonal regions—multiple independent state machines operating in parallel within a composite state.
The Problem:
Consider a phone call that has:
Without orthogonality, you'd need 4 × 2 × 2 = 16 states: ConnectingMutedVideoOn, ConnectingMutedVideoOff, etc. This is the Cartesian product explosion.
Orthogonal Region Notation:
Regions are separated by dashed lines within a composite state:
┌─────────────────────────────────────────────────────────────────┐
│ Active Call │
├─────────────────────────────────────────────────────────────────┤
│ Connection ╎ Audio │
│ ───────────────────── ╎ ───────────────── │
│ ● → [Connecting] → [Stable] ╎ ● → [Unmuted] │
│ ╎ ↕ toggle │
│ ╎ [Muted] │
├────────────────────────────────┼────────────────────────────────┤
│ Video ╎ │
│ ───────────── ╎ │
│ ● → [No Video] ↔ [Video On] ╎ │
└────────────────────────────────┴────────────────────────────────┘
The object is simultaneously in one state from each region: e.g., (Connecting, Unmuted, No Video).
| Concept | Description | Example |
|---|---|---|
| Region | Independent state machine within composite | Audio region, Video region |
| Configuration | Combination of active states across regions | (Connected, Muted, Video On) |
| Fork | Transition entering multiple regions simultaneously | ● → Fork → (state in each region) |
| Join | Transition requiring all regions reach specific states | Join → (waits for all regions) |
| Synchronization | Events affecting multiple regions | disconnect affects all regions |
Fork and Join in State Machines:
When entering an orthogonal composite, you may need to specify initial states for each region:
● → ▬ Fork
├→ [Region1: StateA]
└→ [Region2: StateX]
Similarly, exiting may require all regions to reach specific states:
[Region1: StateB] →┐
├→ ▬ Join → [Next State]
[Region2: StateY] →┘
Synchronization Semantics:
When to Use Orthogonal Regions:
When NOT to Use:
Orthogonal regions model conceptual independence, not thread-level parallelism. The regions run in the same execution context, processing events atomically. True parallelism requires explicit threading architecture beyond the state machine model.
Two special transition types handle events that don't change the external state but may trigger actions.
Self-Transitions:
A self-transition is a transition where the source and target state are the same. The state is exited and re-entered:
┌──────────────────────────────────┐
│ Active │
├──────────────────────────────────┤
│ entry / initialize() │
│ exit / cleanup() │
└──────────────────────────────────┘
│ ↑
└─┘ refresh / reload()
When refresh occurs:
cleanup() executes (exit action)reload() executes (transition action)initialize() executes (entry action)Self-transitions are useful when you need to reset or reinitialize a state.
Internal Transitions:
An internal transition handles an event within a state without exiting or re-entering:
┌──────────────────────────────────────────────┐
│ Active │
├──────────────────────────────────────────────┤
│ entry / initialize() │
│ update / processUpdate() ← internal │
│ log / writeToLog() ← internal │
│ exit / cleanup() │
└──────────────────────────────────────────────┘
When update occurs:
processUpdate() executesInternal transitions are notated in the state's internal behavior compartment, not as arrows.
Choosing Between Self and Internal:
| Scenario | Use Self-Transition | Use Internal Transition |
|---|---|---|
| Need to reinitialize | ✓ | |
| Just handling an event | ✓ | |
| Want exit/entry to run | ✓ | |
| Want do activity to continue | ✓ | |
| Timing-critical (avoid re-entry overhead) | ✓ |
Internal transitions are listed inside the state, not as external arrows. The syntax is: event [guard] / action. They appear in the same compartment as entry/exit/do but represent responses to specific events rather than state lifecycle behaviors.
Creating effective state machines requires systematic thinking about object behavior. Here's a disciplined approach:
Step 1: Identify the Object
Which object's lifecycle are you modeling? Not every class needs a state machine—only those with:
Step 2: Enumerate States
List all distinct conditions the object can be in:
Avoid explosion: if you have 20+ states, consider hierarchical decomposition.
Step 3: Define Events
What stimuli affect this object?
Step 4: Map Transitions
For each (state, event) pair, determine:
Step 5: Validate Completeness
Create a state-event matrix:
| submit | approve | reject | timeout | cancel |
------------|--------|---------|--------|---------|--------|
Draft | →Pend | - | - | →Exp | →Can |
Pending | - | →Apprv | →Rej | →Exp | →Can |
Approved | - | - | - | - | - |
Rejected | - | - | - | - | - |
Cancelled | - | - | - | - | - |
Expired | - | - | - | - | - |
Every cell should be intentionally decided: transition, internal action, or explicit "event ignored in this state".
Step 6: Consider Error Cases
Often forgotten: What happens when things go wrong?
Error states and recovery transitions are essential for robust designs.
A well-designed state machine translates directly to implementation. Modern state machine libraries (XState for JavaScript, Spring State Machine for Java, etc.) can often implement diagrams almost 1:1. This means your state diagram isn't just documentation—it's an executable specification.
State Machine Diagrams provide a rigorous framework for modeling object lifecycles. They prevent state-related bugs by forcing explicit consideration of every state-event combination. Let's consolidate the key concepts:
What's Next:
Now that we understand both Activity Diagrams (for workflows) and State Diagrams (for lifecycles), we need the wisdom to choose between them—and to know when simpler alternatives suffice. The next page provides a decision framework for selecting the appropriate diagram type for any modeling situation.
You now have comprehensive knowledge of UML State Machine Diagrams. You can model object lifecycles with states, transitions, guards, and actions. You understand composite states for hierarchy and orthogonal regions for concurrency. Next, we'll learn when to use each diagram type and how to keep diagrams appropriately simple.