Loading content...
Every method call that expects a response eventually receives one. Return values complete the communication loop, carrying results, status indicators, and data back from callees to callers. Meanwhile, objects don't always reach out to others—sometimes they invoke their own methods to organize internal logic.
This page dives deep into two essential aspects of sequence diagram communication: return values (what comes back) and self-calls (talking to yourself). Mastering these concepts enables you to model detailed method interactions that accurately reflect implementation-level design.
By the end of this page, you will understand how to model return values with appropriate detail, when to show versus hide returns, and how to represent self-calls including recursive algorithms and internal method decomposition.
A return message flows from the callee back to the caller, representing the method's result. In UML, returns use a specific notation that distinguishes them from outgoing calls.
Visual Components:
Notation Summary:
Call: ━━━━▶ (solid line, filled arrowhead)
Return: - - - > (dashed line, open arrowhead)
Return Label Conventions:
Return arrows can be labeled in several ways:
| Style | Example | When to Use |
|---|---|---|
| Value Only | 42.50 | Concrete scenarios, specific test cases |
| Variable | total | When the returned data matters but value doesn't |
| Type | : Money | Emphasizing the return type in API design |
| Variable: Type | total: Money | Complete method signature documentation |
| Description | calculated total | High-level explanation |
| Status | success, failure | Boolean or status results |
Return arrows are optional in UML sequence diagrams. The end of an activation bar implies a return. So when should you explicitly show returns?
Decision Framework:
Consistency Principle:
Once you decide to show returns in a diagram, be consistent. Showing some returns while hiding others creates ambiguity—readers won't know if hidden returns are void methods or simply omitted.
Recommended Approaches:
Even when returns seem "obvious," showing them can clarify who has control at each moment. In complex diagrams with many calls, explicit returns help readers track the unwinding call stack.
Different programming patterns produce different return value semantics. Understanding these patterns helps you model diverse scenarios accurately.
1. Simple Value Return
A method computes and returns a single value:
2. Object Return
A method returns an object that becomes input to subsequent operations:
3. Result Object Pattern
Returning a wrapper that contains both success/failure status and optional data:
4. Optional/Nullable Return
Methods that may or may not find what they're looking for:
The 'alt' fragment (covered fully in the next page) is useful for showing different possible return values. It makes explicit that the method has multiple possible outcomes.
A self-call (or reflexive message) occurs when an object invokes a method on itself. This is common in object-oriented design for:
Visual Representation:
Self-calls form a loop that starts and ends on the same lifeline. When an object is already active (has an activation bar), a self-call creates a nested activation—a narrower bar stacked on the existing one.
Reading the Diagram:
OrderProcessor is initially activated (perhaps by an external call not shown)validateOrder() on itself → nested activation beginscalculateTotal() on itself → another nested activationThe call stack visualization:
|████████████████████| ← Outer activation (public method)
|████████| ← Self-call: validateOrder()
|████████| ← Self-call: calculateTotal()
|████████| ← Self-call: applyDiscounts()
|████████████████████| ← Outer activation ends
Self-calls appear in many design patterns and implementation strategies. Let's examine the most common use cases:
processOrder() calls validate(), charge(), ship() internally.execute() calls doStep1(), doStep2(), etc.sort(list) calls sort(leftHalf) and sort(rightHalf).handleEvent(e) calls transitionTo(newState) on itself.this for chaining. builder.setName(n).setAge(a).build() are self-calls returning the same object.init() calls validateConfig(), setupConnections(), etc.Example: Template Method Pattern
Not every internal method call needs visualization. Show self-calls when they represent significant steps in an algorithm or when understanding the internal decomposition is important for the design discussion.
Recursive algorithms present a special challenge for sequence diagrams. True recursion creates potentially infinite nesting. We handle this with:
loop fragment to represent repeated self-callsApproach 1: Limited Unrolling
Show a few recursive levels explicitly, with a note indicating continuation:
Approach 2: Loop Fragment
For iterative recursion or when the number of iterations is part of the design:
When modeling recursive algorithms, consider stack depth. Deep recursion in a sequence diagram hints at potential stack overflow in implementation. This is a design-level insight worth capturing.
In realistic scenarios, external calls, self-calls, and returns interweave. Let's model a complex method that orchestrates multiple internal operations while interacting with external services.
Scenario: Order Validation
An OrderValidator receives a validation request, performs multiple internal checks by calling its own methods, consults external services, and returns a comprehensive result.
Analysis:
checkFormat, checkBusinessRules) are self-calls with explicit returnsInventoryService.checkAvailability) uses standard call/returnThis diagram demonstrates how self-calls organize internal logic while external calls integrate with services, all with explicit returns showing data flow.
Applying returns and self-calls effectively requires balancing detail with clarity. Here are proven guidelines:
| Aspect | Guideline | Rationale |
|---|---|---|
| Return Visibility | Show returns when they carry meaningful data or influence flow | Avoid cluttering simple diagrams; be explicit when it matters |
| Return Labels | Match detail level to diagram purpose (value, variable, type) | API docs need types; design sketches just need flow |
| Consistency | If you show one return, show all meaningful returns | Mixed visibility creates confusion |
| Self-Call Depth | Limit nesting to 2-3 levels for readability | Deep nesting obscures the diagram |
| Self-Call Purpose | Show self-calls only when decomposition matters for design | Not every private method needs visualization |
| Recursion | Abstract or limit unrolling; use notes for continuation | Infinite recursion can't be fully drawn |
| Activation Bars | Use activation bars with self-calls to show call stack | Clarifies which level is currently executing |
Too few details and the diagram doesn't communicate design intent. Too many details and it becomes unreadable. Aim for the level of detail that answers your design questions without overwhelming the reader.
We've explored how to model what comes back from method calls and how objects communicate with themselves:
What's Next:
With messages, returns, and self-calls covered, we're ready to tackle fragments—the structural elements that add conditional logic, loops, and alternatives to sequence diagrams. The next page explores combined fragments including alt, opt, loop, and par.
You now understand how to model return values and self-calls in sequence diagrams. You can show meaningful returns, represent internal method decomposition, and handle recursive patterns appropriately.