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

bpmnModel(name) { …​ }

Top-level container. Holds processes, collaborations, data stores, and diagram views.

process(id, name) { …​ }

A single executable process. Populated through a ProcessBuilder.

collaboration(name, id) { …​ }

Multi-pool container with message flows. Populated through a CollaborationBuilder.

diagram(name, processId)

A view scoped to one process.

collaborationDiagram(name, collaborationId)

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:

  • EventPositionSTART, INTERMEDIATE, or END

  • EventDefinition — one of 13: NONE, MESSAGE, TIMER, ERROR, ESCALATION, SIGNAL, COMPENSATION, CONDITIONAL, LINK, CANCEL, MULTIPLE, PARALLEL_MULTIPLE, TERMINATE

  • EventBehaviourCATCHING or THROWING

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

startEvent

startEvent(name: String? = null, definition: EventDefinition = NONE): String

endEvent

endEvent(name: String? = null, definition: EventDefinition = NONE): String

intermediateEvent

intermediateEvent(name: String? = null, definition: EventDefinition = NONE, throwing: Boolean = false): String

boundaryEvent

boundaryEvent(attachedTo: String, name: String? = null, definition: EventDefinition = NONE, interrupting: Boolean = true): String

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

task(name, type, block)

TaskType: NONE, USER, SERVICE, SEND, RECEIVE, MANUAL, SCRIPT, BUSINESS_RULE. The trailing block configures loop markers and boundary events via TaskBuilder.

subProcess(name, expanded, triggeredByEvent, transactional, block)

Inner flow nodes and sequence flows are declared inside the block with a nested ProcessBuilder. expanded = true renders the contents inline; transactional = true draws a double frame; triggeredByEvent = true marks an Event Sub-Process.

callActivity(name, calledElement)

References a globally defined process by calledElement. Renders with a thick border.

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

pool(name, id, horizontal, block)

horizontal = true (default) draws a vertical header band on the left; false draws a horizontal header on top. The block configures lanes and the process(processId) reference.

blackBoxPool(name, id)

A participant with no internal process — renders as an empty framed pool.

messageFlow(from, to, name)

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 sourceRef / targetRef not found in process

ERROR

Exclusive/Inclusive gateway with multiple outgoing flows but no defaultFlow

WARNING

Boundary event attachedToRef does not exist

ERROR

Message flow source equals target

ERROR

Participant processRef points to an unknown process

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):

Order Fulfillment

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):

Customer-Supplier

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):

Bestellprozess-Choreographie

Conversation

The most abstract BPMN view: participants (ovals) and conversation nodes (hexagons) connected by undirected conversation links — no ordering, just "who talks to whom about what." Rendered live below (vault source: 03 Bereiche/kUML/Beispiele/36 BPMN Conversation – PdV Kommunikation.md):

PdV-Kommunikationsübersicht

Cross-references