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)
CallActivitiesPer call activity: the called PROCESS_ID plus Inputs / Outputs variable mappings (InputOutputMapping)
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: elementType, previousElements, followingElements, 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.miragon.bpmn.runtime.BpmnEngine
import io.miragon.bpmn.runtime.BpmnError
import io.miragon.bpmn.runtime.BpmnFlow
import io.miragon.bpmn.runtime.BpmnRelations
import io.miragon.bpmn.runtime.BpmnTimer
import io.miragon.bpmn.runtime.InputOutputMapping
import io.miragon.bpmn.runtime.ProcessId
import io.miragon.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 {
    object CallActivityAbortRegistration {
      val PROCESS_ID: ProcessId = ProcessId("abort-registration")

      object Inputs {
        val CHILD_SUBSCRIPTION_ID: InputOutputMapping = InputOutputMapping(target = "childSubscriptionId", source = "subscriptionId")
      }

      object Outputs {
        val ABORT_RESULT: InputOutputMapping = InputOutputMapping(target = "abortResult", source = "childAbortResult")
      }
    }
  }

  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(
      name = "Confirm registration",
      previousElements = listOf("Activity_SendConfirmationMail"),
      followingElements = listOf("EndEvent_SubscriptionConfirmed"),
      parentId = "SubProcess_Confirmation",
      attachedToRef = null,
      attachedElements = listOf("Timer_EveryDay"),
      elementType = "RECEIVE_TASK",
    )
    // ... all elements
  }
}
java
// Generated by bpmn-to-code
package de.emaarco.example;

import io.miragon.bpmn.runtime.BpmnEngine;
import io.miragon.bpmn.runtime.BpmnError;
import io.miragon.bpmn.runtime.BpmnFlow;
import io.miragon.bpmn.runtime.BpmnRelations;
import io.miragon.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 name: String? = null,            // display name of the element, if set
    val previousElements: List<String>,  // element IDs of preceding flow nodes
    val followingElements: List<String>, // element IDs of following flow nodes
    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
    val elementType: String = "UNKNOWN", // BPMN type, e.g. SERVICE_TASK, TIMER_BOUNDARY_EVENT
)

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.

Call-Activity Variable Mappings

Call activities map variables between the parent process and the called child process. Unlike the plain Variables section — which only carries the parent-scope variable name — CallActivities exposes the full mapping: the target (the name the variable gets in the receiving scope) together with its source / sourceExpression (the origin). Each call activity becomes a nested object holding the called PROCESS_ID plus Inputs (variables passed into the child) and Outputs (variables returned to the parent):

kotlin
object CallActivities {
    object CallActivityAbortRegistration {
        val PROCESS_ID: ProcessId = ProcessId("abort-registration")

        object Inputs {
            val CHILD_SUBSCRIPTION_ID: InputOutputMapping = InputOutputMapping(target = "childSubscriptionId", source = "subscriptionId")
            val CHILD_REASON_CODE: InputOutputMapping = InputOutputMapping(target = "childReasonCode", sourceExpression = "\${reasonCode}")
        }

        object Outputs {
            val ABORT_RESULT: InputOutputMapping = InputOutputMapping(target = "abortResult", source = "childAbortResult")
        }
    }
}

Each mapping is an InputOutputMapping; constant names are derived from the target. Inputs / Outputs are only emitted when non-empty.

kotlin
data class InputOutputMapping(
    val target: String,            // destination variable name (always present)
    val source: String? = null,    // origin variable (plain name, or a FEEL expression on Zeebe)
    val sourceExpression: String? = null, // origin expression — Camunda 7 / Operaton only
)

Engine differences

The target is populated the same way for every engine. The origin differs:

  • Camunda 7 / Operaton (camunda:in / camunda:out): source holds a plain variable name, sourceExpression a ${...} expression.
  • Zeebe (zeebe:input / zeebe:output): source holds a FEEL expression (e.g. =orderId) and sourceExpression is always null.

The "pass all variables" mode (variables="all" / propagateAll{Parent,Child}Variables) is not surfaced as constants.

Variable Extraction

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

  • Zeebezeebe:input/zeebe:output inside zeebe:ioMapping, plus inputElement/inputCollection/outputElement/outputCollection on zeebe:loopCharacteristics
  • Camunda 7camunda: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.