Decisions and supported features#
Spring Boot compatibility#
The embedded Spring Boot starter requires Spring Boot 4, matching the Operaton 2.x Spring Boot starters. 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 examples use Operaton 2.1.3, published on Maven Central.
Embedded Spring Boot 4 applications need a working DataSource and transaction manager, for example via
spring-boot-starter-jdbc — since the Spring Boot 4 module split, the JDBC auto-configurations are not brought in
transitively by the engine starter. Applications that persist custom object variables as JSON should add
operaton-engine-plugin-spin and operaton-spin-dataformat-json-jackson; the Jackson-2 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 Operaton, that adapter-level flexibility is narrower once Spin is involved.
Operaton Spin's JSON integration is tied to the Jackson 2 ecosystem. Because of that:
- Embedded Operaton with Spin JSON serialization must stay on Jackson 2 for the adapter-facing serialization path.
- Embedded Operaton with Jackson 3 should not use Spin JSON serialization.
- Embedded Jackson 3 setups should avoid forcing
operaton.bpm.default-serialization-format=application/jsonunless 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-operaton-embedded: embedded example with Spin and Jackson 2examples/java-operaton-embedded-jackson3: embedded example without Spin and with Jackson 3
If an embedded application 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.operaton-embedded.
The adapter starter does not bring an embedded Operaton engine on its own. Add it together with an Operaton embedded setup such as operaton-bpm-spring-boot-starter or operaton-bpm-spring-boot-starter-webapp. With the webapp starter, log in with the admin user configured under operaton.bpm.admin-user (the examples use demo / demo).
Minimal classpath#
<dependencies>
<dependency>
<groupId>dev.bpm-crafters.process-engine-adapters</groupId>
<artifactId>process-engine-adapter-operaton-embedded-spring-boot-starter</artifactId>
<version>${process-engine-adapter-operaton.version}</version>
</dependency>
<dependency>
<groupId>org.operaton.bpm.springboot</groupId>
<artifactId>operaton-bpm-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
</dependencies>
Minimal YAML#
dev:
bpm-crafters:
process-api:
adapter:
operaton-embedded:
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:
operaton-embedded:
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 Operaton Embedded supports the following values in task information meta block, mapped from the Operaton 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 Operaton 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())