ZenUML uses a Markdown-inspired text format to describe sequence diagrams. This reference covers all supported constructs.
- Title
- Participants
- Groups
- Starter
- Messages
- Async Messages
- Return Statements
- Object Creation
- Fragments
- Dividers
- Comments
- Expressions and Conditions
- Annotations
- Unicode and Emoji
A diagram title appears at the top. It must be the very first line.
title My Sequence Diagram
A.method()
The title keyword is only recognized as a directive when it is the first non-comment token in the file and is not followed by ., (, or =.
Participants are the columns (lifelines) in the diagram. They can be declared explicitly in the head section or inferred implicitly from message statements.
Any name used in a message is automatically a participant:
A.method()
// Participants A is inferred
participant A
participant B
Use an @Annotation before the participant name to set its type. The annotation controls the icon displayed on the lifeline header.
@Actor Alice
@Boundary PaymentGateway
@Control OrderService
@Entity Order
@Database UserDB
@Collections Items
@Queue MessageBroker
Supported type annotations: @Actor, @Boundary, @Control, @Entity, @Database, @Collections, @Queue.
Use <<StereotypeName>> syntax to add a stereotype label:
participant <<Service>> OrderService
@Control <<API>> Gateway
Append a hex color code to set the participant's header color:
participant Alice #FF5733
@Actor Bob #4287f5
Use as to give a participant a display name different from its identifier:
participant OrderService as "Order Service"
@Actor user as "End User"
Add an emoji using shortcode syntax [shortcode] or Unicode emoji directly:
participant [rocket] Deployer
participant 🚀 Deployer
Specify a minimum pixel width as an integer:
participant Alice 120
@Type <<Stereotype>> [emoji] ParticipantName Width as "Label" #COLOR
All parts are optional except the name.
Participants can be visually grouped:
group "Frontend" {
@Actor User
@Boundary WebApp
}
group "Backend" {
@Control API
@Entity DB
}
Groups appear as boxes that span the contained participants' lifelines.
The @Starter annotation declares which participant initiates the sequence. Without it, the first message sender is used.
@Starter(Alice)
Alice.placeOrder()
Messages represent method calls between participants.
A.methodName()
The caller is inferred from context (the enclosing participant or _STARTER_).
A -> B.methodName()
A.transfer(amount, currency)
A.create(id=123, name="Alice")
A.process(String message, int count)
result = A.compute()
String result = A.compute()
A.service.doWork()
A message with a { } block shows an activation box and renders nested statements inside:
A.process() {
B.validate()
C.persist()
}
A trailing ; is optional and treated as a no-op (for compatibility):
A.method();
Async messages use the to: content syntax with a colon separator. They render with an open arrowhead.
A -> B: message text
Content extends to end-of-line. The sender is optional:
B: event payload
A --> B: response text
Or using annotation:
@Return A --> B: response
return value
Returns from the current activation. The value is optional:
return
return result
return computedValue
With semicolon:
return value;
Use new to create a new participant instance. The created participant appears at the point in the diagram where new is called.
new OrderService()
service = new OrderService()
OrderService service = new OrderService()
service = new OrderService(config, timeout)
service = new OrderService() {
service.init()
}
Fragments are UML combined fragments — boxes drawn around grouped statements.
if (condition) {
A.method()
} else if (otherCondition) {
B.method()
} else {
C.method()
}
Conditions support full expressions (see Expressions).
The while, for, foreach, forEach, and loop keywords all render as a loop fragment:
while (items.hasNext()) {
process(item)
}
for (i = 0; i < 10; i++) {
A.step()
}
loop (retryCount > 0) {
A.retry()
}
Condition is optional:
while {
A.poll()
}
opt (userIsAdmin) {
Admin.grantAccess()
}
par {
A.fetchData()
B.loadConfig()
}
critical (mutex) {
DB.write()
}
Sections (section or frame keyword) group statements with an optional label:
section(Authentication) {
User -> Auth: login
Auth -> DB: verify
}
frame(Validation) {
A.validate()
}
Anonymous section (just a brace block):
{
A.internal()
}
try {
A.riskyCall()
} catch (IOException e) {
Logger.log(e)
} finally {
Connection.close()
}
Multiple catch blocks are supported. The catch parameter is optional:
try {
A.call()
} catch {
A.handleError()
}
Use ref to reference another sequence diagram by name:
ref(AuthFlow)
ref(PlaceOrder, ProcessPayment)
Dividers are horizontal separator lines with an optional note. A divider must start at column 0 and begin with ==.
== Setup Phase ==
A.init()
== Execution Phase ==
A.run()
Any characters after == are the divider note. Spaces between == and the note are allowed.
Line comments use //:
// This is a comment
A.method() // inline comment
Comments are associated with the following statement and rendered as a small note above it in the diagram.
Conditions inside if, while, opt, par, and critical support:
if (x == y) { ... }
if (count != 0) { ... }
if (value >= threshold) { ... }
if (score < 100) { ... }
if (isReady && !isCancelled) { ... }
if (a || b) { ... }
if (!flag) { ... }
while (retries * delay < timeout) { ... }
if (balance - amount >= 0) { ... }
if (list.isEmpty()) { ... }
if (queue.size() > 0) { ... }
if (item in collection) { ... }
Plain text without operators is also valid as a condition:
if (user is authenticated) { ... }
loop (for each order) { ... }
- Boolean:
true,false - Null:
nil,null - Number:
42,3.14 - Number with unit:
500ms,1s,2GB,100px - Money:
$99.99 - String:
"hello world"
Annotations start with @ and serve multiple purposes:
| Annotation | Purpose |
|---|---|
@Actor |
Participant type: person icon |
@Boundary |
Participant type: boundary icon |
@Control |
Participant type: control icon |
@Entity |
Participant type: entity icon |
@Database |
Participant type: database icon |
@Collections |
Participant type: collections icon |
@Queue |
Participant type: queue icon |
@Starter / @starter |
Designate the initiating participant |
@Return / @return |
Mark an async return message |
@Reply / @reply |
Alias for @Return |
Any other @Name token is treated as a participant type annotation.
Participant names, method names, and labels support Unicode letters (Chinese, Japanese, Korean, Arabic, etc.):
@Actor 用户
用户 -> 系统: 登录请求
系统.validateCredentials(用户名, 密码)
Emoji shortcodes in square brackets can appear in participant names and method names:
participant [rocket] Deploy
[robot].processQueue()
Unicode emoji characters can be used directly:
participant 🚀 Deployment
🤖.start()
See UNICODE_SUPPORT.md for the full list of supported Unicode ranges and usage rules.
Variable modifiers appear in assignments and are parsed but treated as decorative:
const result = A.compute()
readonly config = A.getConfig()
static instance = new Service()
await response = A.fetchAsync()
title Order Processing
@Actor Customer
@Boundary WebApp
@Control OrderService
@Entity OrderDB
@Queue NotificationQueue
group "Frontend" {
Customer
WebApp
}
@Starter(Customer)
// Place order
Customer -> WebApp: submitOrder(cartId)
WebApp.processOrder(cartId) {
// Validate input
if (cart.isEmpty()) {
return error("Empty cart")
}
String orderId = new OrderService(cart) {
OrderService -> OrderDB: persist(order)
return orderId
}
// Notify async
WebApp -> NotificationQueue: orderCreated(orderId)
}
== Confirmation ==
WebApp --> Customer: orderConfirmed(orderId)
try {
Payment.charge(orderId)
} catch (PaymentException e) {
Logger.log(e)
return error("Payment failed")
} finally {
Session.cleanup()
}