Skip to content

Decisions and supported features#

Spring Boot compatibility#

The embedded Spring Boot starter supports Spring Boot 3 and Spring Boot 4. For Spring Boot 4, the scheduler auto-configuration uses adapter-owned threading conditions instead of Spring Boot's removed Threading.VIRTUAL and Threading.PLATFORM enum values.

If spring.threads.virtual.enabled=true and the application runs on Java 21 or newer, the starter contributes a SimpleAsyncTaskScheduler for Spring's default taskScheduler bean. Otherwise, it falls back to a ThreadPoolTaskScheduler.

The public Spring Boot 4 example uses Camunda 7 Community Edition 7.24.0, the last CE release published on Maven Central. Enterprise patch versions require the consuming application to configure Camunda Enterprise repositories and versions explicitly.

Camunda 7.24's Spring Boot starter still references the Spring Boot 3 Hibernate JPA auto-configuration class org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration. Under Spring Boot 4, the embedded adapter starter filters that Camunda auto-configuration and contributes an equivalent compatibility auto-configuration ordered after Spring Boot 4's org.springframework.boot.hibernate.autoconfigure.HibernateJpaAutoConfiguration.

The compatibility layer does not replace Camunda runtime dependencies. Embedded Spring Boot 4 applications still need a working DataSource and transaction manager, for example via spring-boot-starter-jdbc. Applications that persist custom object variables as JSON should add camunda-engine-plugin-spin and camunda-spin-dataformat-json-jackson; the Spring Boot 4 example uses those dependencies to serialize the sample LocalDateTime payload.

Jackson and Spin compatibility#

The adapter starter can serialize adapter payloads with either Jackson 2 or Jackson 3, depending on what the application provides on the classpath. For embedded Camunda 7, that adapter-level flexibility is narrower once Spin is involved.

Camunda Spin's JSON integration is tied to the Jackson 2 ecosystem. Because of that:

  • Embedded Camunda 7 with Spin JSON serialization must stay on Jackson 2 for the adapter-facing serialization path.
  • Embedded Camunda 7 with Jackson 3 should not use Spin JSON serialization.
  • Embedded Jackson 3 setups should avoid forcing camunda.bpm.default-serialization-format=application/json unless they provide a different compatible JSON variable serialization strategy.
  • Embedded Jackson 3 can still work when object variables use Java serialization instead of Spin JSON serialization.

The provided examples reflect these supported combinations:

  • examples/java-c7-embedded-sb4: Spring Boot 4 embedded example with Spin and Jackson 2
  • examples/java-c7-embedded-sb4-jackson3: Spring Boot 4 embedded example without Spin and with Jackson 3

If an embedded Boot 4 application accidentally ends up with both Jackson ecosystems on the classpath, the starter prefers Jackson 3 by default. When Spin is present, provide an explicit AdapterDataConverter bean backed by Jackson 2 so the adapter matches Spin instead of relying on the default selection.

Configuration#

All embedded adapter properties use the prefix dev.bpm-crafters.process-api.adapter.c7embedded.

The adapter starter does not bring an embedded Camunda engine on its own. Add it together with a Camunda 7 embedded setup such as camunda-bpm-spring-boot-starter or camunda-bpm-spring-boot-starter-webapp.

Minimal classpath#

<dependencies>
  <dependency>
    <groupId>dev.bpm-crafters.process-engine-adapters</groupId>
    <artifactId>process-engine-adapter-camunda-platform-c7-embedded-spring-boot-starter</artifactId>
    <version>${process-engine-api.version}</version>
  </dependency>
  <dependency>
    <groupId>org.camunda.bpm.springboot</groupId>
    <artifactId>camunda-bpm-spring-boot-starter</artifactId>
    <version>7.24.0</version>
  </dependency>
</dependencies>

Minimal YAML#

dev:
  bpm-crafters:
    process-api:
      adapter:
        c7embedded:
          enabled: true
          service-tasks:
            delivery-strategy: embedded_scheduled
            worker-id: embedded-worker
          user-tasks:
            delivery-strategy: embedded_scheduled

Delivery strategies#

User tasks#

Value Effect
embedded_scheduled Polls user tasks on the configured schedule and delivers them to subscriptions.
custom Disables the built-in recurring delivery so you can provide your own delivery mechanism.
disabled Disables automatic user-task delivery. The embedded completion/modification APIs are still available because the engine is local.

Service tasks#

Value Effect
embedded_scheduled Uses the built-in scheduler and the local ExternalTaskService to fetch-and-lock external tasks.
custom Disables the built-in recurring fetch loop so you can provide your own delivery mechanism.
disabled Disables automatic service-task delivery. The embedded completion API is still available for tasks delivered by custom code.

Important properties#

enabled#

Turns the embedded adapter on or off. Default: true.

service-tasks.*#

Property Default Description
worker-id required Worker id used for fetch-and-lock and completion calls.
max-task-count 100 Maximum number of external tasks fetched per pull cycle.
lock-time-in-seconds 10 Lock duration for fetched external tasks.
retry-timeout-in-seconds 10 Timeout used by the default failure retry supplier.
retries 3 Initial retry count used by the default failure retry supplier.
delivery-strategy required One of embedded_scheduled, custom, disabled.
schedule-delivery-fixed-rate-in-seconds 13 Polling interval for embedded_scheduled.
execute-initial-pull-on-startup true Triggers one startup pull before the recurring scheduler takes over.

user-tasks.*#

Property Default Description
delivery-strategy required One of embedded_scheduled, custom, disabled.
schedule-delivery-fixed-rate-in-seconds 5 Polling interval for embedded_scheduled.
execute-initial-pull-on-startup true Triggers one startup pull before the recurring scheduler takes over.

Example with explicit scheduling settings#

dev:
  bpm-crafters:
    process-api:
      adapter:
        c7embedded:
          enabled: true
          service-tasks:
            delivery-strategy: embedded_scheduled
            worker-id: embedded-worker
            max-task-count: 100
            lock-time-in-seconds: 10
            retry-timeout-in-seconds: 30
            retries: 3
            execute-initial-pull-on-startup: true
            schedule-delivery-fixed-rate-in-seconds: 10
          user-tasks:
            delivery-strategy: embedded_scheduled
            execute-initial-pull-on-startup: true
            schedule-delivery-fixed-rate-in-seconds: 10

Message Correlation#

Correlation API implementation support the following restrictions:

Key Value Description
tenantId The id of the tenant Correlates messages for process instances with given tenant id.
withoutTenantId none If restriction is present, correlate only with process instances without tenant id.
useGlobalCorrelationKey true or false If set to false (default if not set), correlate using local variables, use global process variable otherwise.

Task Information#

Currently, the Process Engine Adapter C7 Embedded supports the following values in task information meta block, mapped from the Camunda C7 engine:

The TaskInformation.getMeta() provides meta information about the task in form of a Map<String, String> for maximum compatibility. The Original Type column denotes the real type, you want to access if reading the field. For this purpose, TaskInformation offers special access methods getMetaValueAsOffsetDate and getMetaValueAsStringSet.

User Tasks#

Key Original Type Description Example
activityId String Id of the element in BPMN (Task definition key) approve_user_task
processDefinitionId String Id of process definition (given at deployment time) approval_process:912834729348
processDefinitionKey String Id of the process element in BPMN (Process Definition key) approval_process
tenantId String Tenant Id my_tenant
taskName String Name of the user task (from BPMN or modified by the create listener) Approve Order
taskDescription String Description of the user task (from BPMN or modified by the create listener) Approve provided order.
assignee String Assignee of the user task USER12345
candidateUsers Set Set of candidate users, separated by a , USER12345,USER12346,USER12347
candidateGroups Set Set of candidate groups, separated by a , marketing,sales
creationDate OffsetDateTime Time stamp of task creation formatted as ISO-8601 in UTC 2025-05-01T10:00:00.000Z
followUpDate OffsetDateTime Time stamp of task follow-up formatted as ISO-8601 in UTC 2025-05-02T10:00:00.000Z
dueDate OffsetDateTime Time stamp of task due formatted as ISO-8601 in UTC 2025-05-05T10:00:00.000Z
lastUpdatedDate OffsetDateTime Time stamp of task last update formatted as ISO-8601 in UTC 2025-05-05T10:00:00.000Z

Service Tasks#

Key Original Type Description Example
activityId String Id of the element in BPMN (Task definition key) approve_user_task
processDefinitionId String Id of process definition (given at deployment time) approval_process:912834729348
processDefinitionKey String Id of the process element in BPMN (Process Definition key) approval_process
tenantId String Tenant Id my_tenant
topicName String Topic name (from BPMN) for external task topic_approve
creationDate OffsetDateTime Time stamp of task creation formatted as ISO-8601 in UTC 2025-05-01T10:00:00.000Z

Engine Command Executor#

EngineCommandExecutor is an embedded-adapter-specific class that controls how the four core API calls (correlateMessage, sendSignal, startProcess, deploy) are dispatched to the embedded Camunda 7 engine.

By default, all engine calls are submitted asynchronously to ForkJoinPool.commonPool(). This means they run on a different thread from the caller and do not participate in the caller's @Transactional context — a rollback on the calling thread will not roll back the engine operation.

Customizing execution#

Provide a Spring bean of type EngineCommandExecutor to override the default. The auto-configured default is annotated with @ConditionalOnMissingBean, so your bean takes precedence automatically.

Same-thread (synchronous) execution — engine calls run on the calling thread and honour @Transactional:

@Bean
fun engineCommandExecutor(): EngineCommandExecutor =
  EngineCommandExecutor(Executor { it.run() })

Virtual-thread execution — lightweight concurrency without pinning platform threads:

@Bean
fun engineCommandExecutor(): EngineCommandExecutor =
  EngineCommandExecutor(Executors.newVirtualThreadPerTaskExecutor())