BPMN 2.0 DSL Reference
BPMN 2.0 is kUML’s fourth modelling language, alongside UML 2.x, SysML 2 and C4. It is a standalone, pure-Kotlin metamodel — not a profile on UML activities — that implements the OMG Business Process Model and Notation 2.0 standard. All four BPMN diagram types are supported: Process, Collaboration, Choreography, and Conversation.
The BPMN DSL entry point is bpmnModel:
bpmnModel("Order Management") {
process(id = "p_order", name = "Order Process") {
// flow nodes and sequence flows go here
}
diagram("Order Process", processId = "p_order")
}
Concepts at a glance
| Concept | Notes |
|---|---|
|
Top-level container. Holds processes, collaborations, data stores, and diagram views. |
|
A single executable process. Populated through a |
|
Multi-pool container with message flows. Populated through a |
|
A view scoped to one process. |
|
A view scoped to one collaboration (renders swimlanes). |
Every builder method that creates an element returns its auto-generated, deterministic id
(for example p_order_task_1). Capture the return value in a val and pass it to
sequenceFlow / flowsTo. There is no manual id management.
Events — the Event-Matrix design
kUML models BPMN’s ~50 notational event variants as a single BpmnEvent class carrying a
triplet:
-
EventPosition—START,INTERMEDIATE, orEND -
EventDefinition— one of 13:NONE,MESSAGE,TIMER,ERROR,ESCALATION,SIGNAL,COMPENSATION,CONDITIONAL,LINK,CANCEL,MULTIPLE,PARALLEL_MULTIPLE,TERMINATE -
EventBehaviour—CATCHINGorTHROWING
Instead of 36+ subclasses, one data class with init{} guards enforces the legal
combinations (a START event is always catching; TERMINATE is only valid at END; LINK
only at INTERMEDIATE; and so on). This keeps the metamodel small and trivially extensible.
bpmnModel("Order Flow") {
process(id = "p_order", name = "Order Process") {
val received = startEvent("Order Received", definition = EventDefinition.MESSAGE)
val timeout = intermediateEvent("Wait 24h", definition = EventDefinition.TIMER)
val done = endEvent("Order Shipped")
val aborted = endEvent("Cancelled", definition = EventDefinition.TERMINATE)
received flowsTo timeout flowsTo done
}
diagram("Order Flow", processId = "p_order")
}
The event builders:
| Builder | Signature |
|---|---|
|
|
|
|
|
|
|
|
A boundary event attaches to an activity by id. Non-interrupting boundary events render with a dashed border:
val review = subProcess(name = "Review Cycle", expanded = true) { /* ... */ }
val timer = boundaryEvent(review, name = "Deadline",
definition = EventDefinition.TIMER, interrupting = false)
val expired = endEvent("Review Expired", definition = EventDefinition.TERMINATE)
timer flowsTo expired
Tasks and activities
bpmnModel("Fulfilment") {
process(id = "p", name = "Fulfilment") {
val check = task("Check Stock", type = TaskType.SERVICE)
val pick = task("Pick & Pack", type = TaskType.MANUAL)
val notify = task("Notify Customer", type = TaskType.SEND)
val approve = task("Approve Refund", type = TaskType.USER) {
standardLoop(testBefore = true) // loop marker ↻
}
val batch = task("Process Items", type = TaskType.SCRIPT) {
multiInstance(sequential = false) // parallel ‖ marker
}
check flowsTo pick flowsTo notify
}
diagram("Fulfilment", processId = "p")
}
| Builder | Notes |
|---|---|
|
|
|
Inner flow nodes and sequence flows are declared inside the block with a nested
|
|
References a globally defined process by |
TaskBuilder (the trailing block on task) offers:
-
standardLoop(testBefore: Boolean = true, …)— a while/until loop marker. -
multiInstance(sequential: Boolean = false, …)— a parallel (‖) or sequential (≡) marker. -
boundaryEvent(eventId)— attach a boundary event that was declared elsewhere.
Gateways
bpmnModel("Routing") {
process(id = "p", name = "Routing") {
val start = startEvent("Start")
val split = gateway(GatewayType.EXCLUSIVE, name = "In Stock?")
val ship = task("Ship Order", type = TaskType.USER)
val order = task("Reorder Stock", type = TaskType.SERVICE)
val join = gateway(GatewayType.EXCLUSIVE)
val end = endEvent("Done")
start flowsTo split
sequenceFlow(split, ship, condition = "stock > 0", name = "yes")
sequenceFlow(split, order, condition = "stock == 0", name = "no", default = true)
ship flowsTo join
order flowsTo join flowsTo end
}
diagram("Routing", processId = "p")
}
gateway(type, name, default) accepts the five BPMN gateway kinds:
EXCLUSIVE (X), INCLUSIVE (O), PARALLEL (+), EVENT_BASED (⊚), COMPLEX (✱).
The optional default parameter names the default outgoing flow.
Sequence flows and the flowsTo infix
There are two equivalent ways to connect flow nodes:
// Explicit — supports condition, name, and default flag:
sequenceFlow(from = split, to = ship, condition = "stock > 0", name = "yes")
// Infix chain — concise, left-associative, returns the target:
start flowsTo check flowsTo gateway
sequenceFlow is a Pattern A edge: it lives on the model itself (not only on the diagram),
so it is available to the constraint checker, the XML exporter, and the layout bridge.
Data objects and data stores
bpmnModel("Invoicing") {
val ledger = dataStore(name = "Ledger", unlimited = true) // model-level RootElement
process(id = "p", name = "Invoicing") {
val invoice = dataObject(name = "Invoice")
val emit = task("Emit Invoice", type = TaskType.SERVICE)
dataAssociation(from = emit, to = invoice)
}
diagram("Invoicing", processId = "p")
}
dataObject(name, collection) is process-scoped; dataStore(id, name, unlimited) is a
model-level RootElement that can be referenced from multiple processes.
Collaboration — pools, lanes, message flows
A collaboration places two or more participants (pools) side by side and connects them with
message flows. A pool either references an internal process or is a Black-Box Pool
(processRef = null).
bpmnModel("Customer-Supplier Collaboration") {
process(id = "p_customer", name = "Customer") {
val place = startEvent("Place Order", definition = EventDefinition.MESSAGE)
val receive = task("Receive Goods", type = TaskType.USER)
place flowsTo receive flowsTo endEvent("Order Complete")
}
process(id = "p_supplier", name = "Supplier") {
val order = startEvent("Receive Order", definition = EventDefinition.MESSAGE)
val ship = task("Ship Goods", type = TaskType.SERVICE)
order flowsTo ship flowsTo endEvent("Goods Shipped", definition = EventDefinition.MESSAGE)
}
collaboration(name = "Order Flow") {
val customer = pool(name = "Customer") { process("p_customer") }
val supplier = pool(name = "Supplier") { process("p_supplier") }
messageFlow(from = "p_customer_start_1", to = "p_supplier_start_1", name = "Purchase Order")
messageFlow(from = "p_supplier_end_1", to = "p_customer_task_1", name = "Shipping Notice")
}
collaborationDiagram("Customer-Supplier", collaborationId = "collab_1")
}
| Builder | Notes |
|---|---|
|
|
|
A participant with no internal process — renders as an empty framed pool. |
|
Connects elements in different pools. Renders as a dashed line with an open arrowhead and a small circle at the source. Message flows within a single pool are invalid — use a sequence flow instead. |
Lanes
Lanes subdivide a pool. A lane references flow nodes by id (contains(…)) — it does not
own them. Lanes nest via inner lane { … } blocks.
collaboration(name = "Bank") {
pool(name = "Bank") {
process("p_bank")
lane(name = "Front Office") {
contains("p_bank_task_1", "p_bank_task_2")
}
lane(name = "Back Office") {
contains("p_bank_task_3")
lane(name = "Risk") { contains("p_bank_task_4") } // nested child lane
}
}
}
Constraint checking
BpmnConstraintChecker.check(model) returns a list of ConstraintViolation`s with
`ERROR or WARNING severity. Seven rules ship:
| Rule | Severity |
|---|---|
Process has no start event |
WARNING |
Process has no end event |
WARNING |
Sequence flow |
ERROR |
Exclusive/Inclusive gateway with multiple outgoing flows but no |
WARNING |
Boundary event |
ERROR |
Message flow source equals target |
ERROR |
Participant |
ERROR |
The CLI runs these checks automatically when rendering a BPMN model and prints any violations as warnings.
Rendering and export
A BPMN model renders to SVG (with full OMG notation), PNG, and LaTeX/TikZ exactly like the other modelling languages:
kuml render order-process.kuml.kts --format svg
kuml render order-process.kuml.kts --format png --width 1600
kuml render order-process.kuml.kts --format latex --latex-standalone
BPMN models also round-trip through standard BPMN 2.0 XML — see BPMN 2.0 XML I/O.
Worked examples — all four diagram types
Process
A full order-fulfilment process exercising the event matrix, all five gateway kinds,
data objects/stores, a loop marker, and a call activity. Rendered live below (vault
source: 03 Bereiche/kUML/Beispiele/30 BPMN Process – Order Fulfillment.md):
Collaboration
Two processes (Customer, Supplier) plus a black-box pool (an external carrier), lanes
inside the Supplier pool, and message flows between pools. Rendered live below (vault
source: 03 Bereiche/kUML/Beispiele/32 BPMN Collaboration – Customer und Supplier.md):
Choreography
Models the message exchange between participants without owning any single
participant’s internal process — three choreography tasks connected by sequence flow,
each carrying its own initiating/receiving messages. Rendered live below (vault source:
03 Bereiche/kUML/Beispiele/37 BPMN Choreography – Bestellprozess.md):
Cross-references
-
BPMN 2.0 XML I/O — import from / export to OMG BPMN 2.0 XML
-
CLI Reference —
kuml render,kuml import,kuml export