Skip to content

πŸ“¦ Generated API ​

One file per BPMN process, containing type-safe constants for all extracted elements.

Structure ​

The generated Process API is an object (Kotlin) or class (Java) with nested sections:

SectionContents
PROCESS_IDThe process identifier from the BPMN model
PROCESS_ENGINEThe engine the API was generated for, as a typed BpmnEngine enum (ZEEBE, CAMUNDA_7, OPERATON)
ElementsAll flow node IDs (tasks, events, gateways, subprocesses)
CallActivitiesCalled process IDs for call activity elements
MessagesMessage names from message events and receive tasks
ServiceTasksWorker types / topics / delegate expressions from service tasks
TimersTimer configurations with type and value (BpmnTimer)
ErrorsError definitions with name and code (BpmnError)
CompensationsCompensation event IDs
SignalsSignal names from signal events
VariablesPer-element variables, split into Inputs / Outputs sub-objects by direction
FlowsSequence flows with sourceRef, targetRef, conditionExpression, isDefault
RelationsPer-element topology: incoming, outgoing, parentId, attachedToRef, attachedElements

Sections are only included when the BPMN model contains matching elements.

Full Example ​

Given a newsletter subscription BPMN process, bpmn-to-code generates:

kotlin
// Generated by bpmn-to-code
@file:Suppress("unused")

package de.emaarco.example

import io.github.emaarco.bpmn.runtime.BpmnEngine
import io.github.emaarco.bpmn.runtime.BpmnError
import io.github.emaarco.bpmn.runtime.BpmnFlow
import io.github.emaarco.bpmn.runtime.BpmnRelations
import io.github.emaarco.bpmn.runtime.BpmnTimer
import io.github.emaarco.bpmn.runtime.VariableName

object NewsletterSubscriptionProcessApi {
  const val PROCESS_ID: String = "newsletterSubscription"
  val PROCESS_ENGINE: BpmnEngine = BpmnEngine.ZEEBE

  object Elements {
    const val ACTIVITY_CONFIRM_REGISTRATION: String = "Activity_ConfirmRegistration"
    const val ACTIVITY_SEND_CONFIRMATION_MAIL: String = "Activity_SendConfirmationMail"
    const val ACTIVITY_SEND_WELCOME_MAIL: String = "Activity_SendWelcomeMail"
    const val CALL_ACTIVITY_ABORT_REGISTRATION: String = "CallActivity_AbortRegistration"
    const val END_EVENT_REGISTRATION_COMPLETED: String = "EndEvent_RegistrationCompleted"
    const val END_EVENT_SUBSCRIPTION_CONFIRMED: String = "EndEvent_SubscriptionConfirmed"
    const val START_EVENT_SUBMIT_REGISTRATION_FORM: String = "StartEvent_SubmitRegistrationForm"
    const val SUB_PROCESS_CONFIRMATION: String = "SubProcess_Confirmation"
    const val TIMER_EVERY_DAY: String = "Timer_EveryDay"
    // ... all other elements
  }

  object CallActivities {
    const val CALL_ACTIVITY_ABORT_REGISTRATION: String = "abort-registration"
  }

  object Messages {
    const val MESSAGE_FORM_SUBMITTED: String = "Message_FormSubmitted"
  }

  object ServiceTasks {
    const val NEWSLETTER_SEND_CONFIRMATION_MAIL: String = "#{newsletterSendConfirmationMail}"
    const val NEWSLETTER_SEND_WELCOME_MAIL: String = "\${newsletterSendWelcomeMail}"
    const val NEWSLETTER_REGISTRATION_COMPLETED: String = "newsletter.registrationCompleted"
  }

  object Timers {
    val TIMER_EVERY_DAY: BpmnTimer = BpmnTimer("Duration", "PT1M")
    val TIMER_AFTER_3_DAYS: BpmnTimer = BpmnTimer("Duration", "\${testVariable}")
  }

  object Errors {
    val ERROR_INVALID_MAIL: BpmnError = BpmnError("Error_InvalidMail", "500")
  }

  object Compensations {
    const val COMPENSATION_END_EVENT_REGISTRATION_ABORTED: String = "CompensationEndEvent_RegistrationAborted"
  }

  object Signals {
    const val SIGNAL_REGISTRATION_NOT_POSSIBLE: String = "Signal_RegistrationNotPossible"
  }

  object Variables {
    object ActivitySendConfirmationMail {
      object Inputs {
        val SUBSCRIPTION_ID: VariableName = VariableName("subscriptionId")
      }
    }

    object StartEventSubmitRegistrationForm {
      object Outputs {
        val SUBSCRIPTION_ID: VariableName = VariableName("subscriptionId")
      }
    }
  }

  object Flows {
    val FLOW_05_I_3_X_1_Y: BpmnFlow = BpmnFlow(
      id = "Flow_05i3x1y",
      sourceRef = "StartEvent_RequestReceived",
      targetRef = "Activity_SendConfirmationMail",
    )
    // ... all sequence flows
  }

  object Relations {
    val ACTIVITY_CONFIRM_REGISTRATION: BpmnRelations = BpmnRelations(
      incoming = listOf("Activity_SendConfirmationMail"),
      outgoing = listOf("EndEvent_SubscriptionConfirmed"),
      parentId = "SubProcess_Confirmation",
      attachedToRef = null,
      attachedElements = listOf("Timer_EveryDay"),
    )
    // ... all elements
  }
}
java
// Generated by bpmn-to-code
package de.emaarco.example;

import io.github.emaarco.bpmn.runtime.BpmnEngine;
import io.github.emaarco.bpmn.runtime.BpmnError;
import io.github.emaarco.bpmn.runtime.BpmnFlow;
import io.github.emaarco.bpmn.runtime.BpmnRelations;
import io.github.emaarco.bpmn.runtime.BpmnTimer;

public class NewsletterSubscriptionProcessApi {
    public static final String PROCESS_ID = "newsletterSubscription";
    public static final BpmnEngine PROCESS_ENGINE = BpmnEngine.ZEEBE;

    public static class Elements {
        public static final String ACTIVITY_CONFIRM_REGISTRATION = "Activity_ConfirmRegistration";
        public static final String ACTIVITY_SEND_WELCOME_MAIL = "Activity_SendWelcomeMail";
        // ... same constants as Kotlin, with Java syntax
    }

    public static class Messages {
        public static final String MESSAGE_FORM_SUBMITTED = "Message_FormSubmitted";
    }

    // ... same structure for ServiceTasks, Timers, Errors, Compensations, Signals, Variables, Flows, Relations
}

Flows ​

The Flows section contains every sequence flow in the process as a BpmnFlow data class:

kotlin
data class BpmnFlow(
    val id: String,
    val sourceRef: String,
    val targetRef: String,
    val conditionExpression: String? = null,
    val isDefault: Boolean = false,
)

Use Flows to build process-aware test fixtures or to inspect routing logic in your application code.

Relations ​

The Relations section maps every flow node to its topology information via BpmnRelations:

kotlin
data class BpmnRelations(
    val incoming: List<String>,      // IDs of incoming sequence flows or elements
    val outgoing: List<String>,      // IDs of outgoing sequence flows or elements
    val parentId: String?,           // parent subprocess ID, if any
    val attachedToRef: String?,      // element this boundary event is attached to
    val attachedElements: List<String>, // boundary events attached to this element
)

This is useful for traversing process topology in tests, validations, or analysis tools.

Per-Element Variables with Direction ​

Every variable is associated with the element that declares it and with the direction in which it flows. The Variables.<Element> object splits its constants into nested Inputs / Outputs sub-objects so consumers can tell whether a variable is read by the element or written by it:

kotlin
object Variables {
    object ActivitySendConfirmationMail {
        object Inputs {
            val SUBSCRIPTION_ID: VariableName = VariableName("subscriptionId")
        }
    }

    object StartEventSubmitRegistrationForm {
        object Outputs {
            val SUBSCRIPTION_ID: VariableName = VariableName("subscriptionId")
        }
    }
}

A sub-object is only emitted when it contains at least one variable β€” one-sided splits (Inputs only or Outputs only) are legal. An element without any directional variables is omitted from Variables entirely.

Variable Extraction ​

Variables are extracted from direction-aware BPMN sources. See the engine-specific pages for details:

  • Zeebe β€” zeebe:input/zeebe:output inside zeebe:ioMapping, plus inputElement/inputCollection/outputElement/outputCollection on zeebe:loopCharacteristics
  • Camunda 7 β€” camunda:inputParameter/camunda:outputParameter, camunda:in/camunda:out, multi-instance camunda:collection/camunda:elementVariable, and the additionalInputVariables / additionalOutputVariables extension properties
  • Operaton β€” same patterns as Camunda 7, using the operaton: namespace

INFO

bpmn-to-code only extracts variables from explicit BPMN definitions. Variables only referenced in expressions (sequence flows, gateway conditions, script tasks) are intentionally ignored. This is by design β€” the BPMN model should be the single source of truth for its variable contract.

Model Merging ​

When multiple BPMN files share the same processId, bpmn-to-code merges them into a single API. The generated API contains the superset of all elements across all files.

This is useful for process variants (e.g. dev vs prod configurations) that share the same process identifier. If multiple files define the same processId, use the variantName BPMN extension property to distinguish them and prevent silent overwrites in the JSON output.