Loading learning content...
Lifelines establish who participates in an interaction. But participants don't accomplish anything in isolation—they collaborate by exchanging messages. In sequence diagrams, messages are the arrows that connect lifelines, representing the flow of control, information, and responsibility between objects.
Understanding messages is essential because they encode the actual behavior of your system. Each message represents a method call, a signal, or a response. Together, they tell the complete story of how a feature is implemented through object collaboration.
By the end of this page, you will master the different types of messages in sequence diagrams: synchronous calls, return messages, and message signatures. You'll understand how to read and create message flows that accurately represent object communication.
In sequence diagrams, a message represents a communication between two participants. It typically corresponds to:
Visual Representation:
Messages are drawn as horizontal arrows between lifelines. The arrow points from the sender (caller) to the receiver (callee). The message name (usually a method name) labels the arrow.
Key Properties of Messages:
In object-oriented design, most messages in sequence diagrams correspond directly to method invocations. When you see A->>B: processOrder(), think: "Object A calls the processOrder() method on object B." This mental mapping connects your diagrams to code.
The most common message type is the synchronous message, represented by a solid arrow with a filled arrowhead. A synchronous message indicates that the sender:
This is the standard method call in most programming languages. When you write result = objectB.processOrder(data), you're making a synchronous call—your code pauses until processOrder() finishes and returns.
Reading the Diagram:
Controller synchronously calls calculateTotal(items) on ServiceService activates and synchronously calls findPrices(itemIds) on RepositoryRepository activates, processes, then returns prices[] to ServiceService processes the prices and returns totalAmount to ControllerNotice how the caller (Controller) must wait through the entire chain. Only after Service returns does Controller continue execution.
Synchronous Call Characteristics:
| Aspect | Behavior | Design Implication |
|---|---|---|
| Blocking | Caller waits until callee returns | Long operations block the caller; consider timeout handling |
| Direct Coupling | Caller holds reference to callee | Creates compile-time dependencies between classes |
| Sequential | Operations execute in strict order | Easier to reason about, but limits parallelism |
| Return Value | Callee can return data to caller | Enables request-response patterns |
A return message represents the response flowing back from a method call. It uses a dashed arrow with an open arrowhead, pointing from the callee back to the caller.
Visual Distinction:
| Message Type | Arrow Style | Direction |
|---|---|---|
| Synchronous call | Solid line, filled head | Caller → Callee |
| Return | Dashed line, open head | Callee → Caller |
When to Show Returns:
Return messages are optional in sequence diagrams. Two conventions exist:
Return Message Labels:
Return arrows are typically labeled with:
prices[], totalAmount, trueorder created, validation passedresult = ...Best Practice: Be consistent within a diagram. If you show some returns, show all meaningful ones. If you omit returns, do so throughout (except for critical decision points).
Many teams omit return arrows for simple synchronous calls, treating returns as implied. The end of an activation bar indicates the method has returned. This keeps diagrams cleaner while preserving essential information.
Message labels can include varying levels of detail, from simple method names to complete signatures with parameters and return types.
Levels of Detail:
| Style | Example | When to Use |
|---|---|---|
| Simple Name | processOrder() | Early design; focus is on flow, not specifics |
| With Parameters | processOrder(orderId, items) | When parameter data matters to understanding |
| Typed Parameters | processOrder(orderId: UUID, items: LineItem[]) | API design; formal documentation |
| With Return Type | processOrder(...): OrderResult | When return type is architecturally significant |
| Full Signature | processOrder(orderId: UUID, items: LineItem[]): Result<Order> | Detailed design; close to implementation |
Parameter Passing:
When parameters are shown, they represent the data flowing from caller to callee:
controller -> service: calculateShipping(order, destination)
This tells us that the calculateShipping method receives two arguments: the order being shipped and the destination address. The reader understands what information is available inside the called method.
Showing Actual Values:
For scenario-specific diagrams (showing a particular use case execution), you might show actual values:
controller -> service: calculateShipping(order#1234, "New York")
This is useful for illustrating specific test cases or debugging scenarios.
Use simple names for early design exploration and whiteboard discussions. Add parameters when they clarify the interaction. Reserve full signatures for detailed design documents that drive implementation. More detail isn't always better—it's about communicating the right information for your audience.
A self-message (or self-call) occurs when an object invokes a method on itself. This is drawn as an arrow that loops back to the same lifeline.
Visual Representation:
The arrow starts from the activation bar, curves out, and returns to the same lifeline, typically creating a nested activation bar.
When Self-Calls Occur:
Internal Method Decomposition — A public method delegates to private helper methods:
process() calls this.validate() then this.execute()Recursive Algorithms — A method calls itself with different parameters:
sort(list) calls sort(leftHalf) and sort(rightHalf)Template Method Pattern — A base method defines structure, calling overridable steps:
handleRequest() calls this.preProcess(), this.doHandle(), this.postProcess()State Transitions — An object triggers its own state changes:
processEvent() calls this.transitionTo(newState)Self-calls add visual complexity. Show them when the internal decomposition is essential to understanding the design. Omit them for trivial internal operations. Ask: "Does the reader need to know about this internal call to understand the scenario?"
Nested Activation Bars:
When an active object calls itself, a new, narrower activation bar is stacked on top of the existing one. This visualizes the growing call stack:
|████| ← Outer activation (handleRequest)
|██████| ← Inner activation (self-call to validate)
|████| ← Back to outer after validate returns
Each nested bar represents a deeper level in the call stack within the same object.
Sequence diagrams derive their power from the vertical axis, which represents time. This temporal dimension establishes causality and ordering between messages.
The Vertical Time Axis:
Causality and Ordering:
The vertical ordering establishes causal relationships:
Reading Temporal Flow:
In the diagram above:
first() happens before second() (causally—B can't call C until A calls B)second() completes (result2) before first() completes (result1)third() happens after first() returns (A waits for result1 before calling C)Horizontal Distance Is Not Time:
A common misconception: the horizontal distance between messages has no meaning. Two arrows at different horizontal positions but the same vertical level represent events at the same logical time (or unordered events).
Sequence diagrams show logical temporal ordering, not real-time duration. A tall activation bar doesn't mean a slow operation. Use annotations or notes if you need to indicate actual timing constraints (e.g., "must complete within 100ms").
Let's consolidate all the message types we've discussed, plus a preview of asynchronous messages (covered in the next page):
| Message Type | Arrow Notation | Meaning | Caller Behavior |
|---|---|---|---|
| Synchronous Call | Solid line, filled arrowhead → | Invoke method, wait for completion | Blocks until callee returns |
| Return | Dashed line, open arrowhead ← | Method result sent back | Caller resumes execution |
| Create | Dashed line to header box | Object instantiation | New lifeline begins |
| Destroy | Line ending with X | Object termination | Lifeline ends |
| Self-Call | Arrow looping back to same lifeline | Internal method invocation | Nested activation on same object |
| Asynchronous | Solid line, open arrowhead → | Send signal, don't wait (next page) | Continues immediately |
Distinguishing Arrow Heads:
Note: Different UML tools may vary slightly in their notation. The key is to be consistent within your diagrams and establish conventions with your team.
Many teams simplify: solid arrows for calls, dashed arrows for returns. The filled vs. open arrowhead distinction for sync vs. async is important but often overlooked in informal diagrams. When precision matters (design docs, architecture reviews), use the correct arrow styles.
Let's apply message concepts to model a user authentication flow. This example demonstrates synchronous calls, returns, and the message signatures that communicate essential information.
The Scenario:
A user submits login credentials. The system validates the credentials, checks account status, generates a session token, and returns the result.
Message Analysis:
| Message | Type | Purpose |
|---|---|---|
login(username, password) | Sync call | User initiates authentication |
authenticate(credentials) | Sync call | Controller delegates to auth service |
findByUsername(username) | Sync call | Load user from database |
user | Return | User entity returned |
verifyPassword(...) | Self-call | Internal password validation |
checkAccountStatus(user) | Self-call | Internal status verification |
generateToken(user) | Sync call | Create session token |
AuthResult(success, token) | Return | Complete auth result |
LoginResponse(token) | Return | Final response to user |
Design Insights from the Diagram:
We've explored how objects communicate through messages—the arrows that bring sequence diagrams to life. Here's what we've covered:
What's Next:
Synchronous calls are just one communication style. Modern systems increasingly rely on asynchronous communication—messages that don't block the sender. The next page explores asynchronous calls, their notation, and when each style is appropriate.
You now understand how objects communicate through messages in sequence diagrams. You can read and create message flows using synchronous calls, returns, and self-messages, with appropriate levels of signature detail. Next, we'll contrast synchronous with asynchronous communication.