Asynchronous Execution
Introduction
Flowable provides the possibility to mark tasks and other BPMN elements as asynchronous. This controls when and how the element is executed, specifically by introducing transaction boundaries in the process execution. Understanding async execution is key to building robust, production-grade processes.
A common misconception is that "async" means the process will skip ahead and execute other steps in parallel. In reality, the process flow semantics remain unchanged — async controls transaction boundaries, not the order of execution.
Synchronous vs Asynchronous Execution
Synchronous (default)
By default, the Flowable engine executes as far as possible within a single transaction — from the triggering action (e.g., completing a user task) all the way to the next wait state (e.g., the next user task). If any step fails, the entire transaction rolls back, undoing all progress.
This means:
- The user sees errors immediately
- Long-running operations block the calling thread (e.g., the UI shows a spinner)
- A failure in step 5 of 10 rolls back steps 1 through 4 as well
Asynchronous
When an element is marked as async, the engine introduces a transaction boundary at that point. The engine:
- Commits the current transaction, persisting all progress made so far
- Creates a job in the database representing the work to be done
- Returns control to the caller immediately
- The Async Executor picks up the job and executes the element in a new transaction on a separate thread
This means:
- Users get immediate feedback — the API call returns quickly
- Previously completed work is safe — a failure in the async step does not roll back earlier steps
- The engine takes responsibility for retrying failures automatically
- Shorter transactions reduce database lock contention
The Async Flag
Most BPMN elements support the async flag. There are two variants:
Async (before)
When Async is enabled on an element, the engine pauses execution before the element runs. It persists the current state, creates a job, and returns. The Async Executor later picks up the job and executes the element in a new transaction.
Async leave (after)
When Async leave is enabled, the element itself executes synchronously, but its completion (including leaving the element and any end execution listeners) happens asynchronously in a new transaction.
When to Use Async
Use async when:
- The element calls an external service that may be slow or unreliable (e.g., an HTTP call, a third-party API)
- You want the user to get immediate feedback rather than waiting for downstream processing
- You want the engine to handle retries automatically for transient failures
- You want to protect already-completed work from being rolled back by a later failure
Avoid async when:
- The operation requires immediate confirmation that it completed successfully (e.g., security-critical operations like revoking permissions)
- You need the calling thread to know the result of the operation before proceeding
The Async Executor
The Async Executor is the engine component responsible for picking up and executing async jobs. It runs within the Flowable application and manages:
- Job acquisition: Periodically scanning for jobs that are ready to execute
- Job execution: Running jobs via a configurable thread pool
- Job persistence: All jobs are stored in the database and survive application restarts
- Error handling: Automatically retrying failed jobs
Job Types
The engine uses different database tables for different job states:
| Job Type | Description |
|---|---|
| Async Jobs | Created when an async-marked element is reached. Represents work ready to be executed. |
| Timer Jobs | Created by timer events (boundary timers, intermediate catch timers). A dedicated thread checks for timers whose due date has passed, then converts them into async jobs. |
| Suspended Jobs | Jobs belonging to suspended process instances are moved here so they don't interfere with normal job acquisition. |
| Dead Letter Jobs | Jobs that have exhausted all retry attempts. Require manual intervention. |
Error Handling and Retries
When an async job fails during execution:
- The job is converted into a timer job with a future due date (providing a cooling-off period)
- When the timer fires, the job is converted back into an async job for retry
- The job has a retry counter (default: 3 retries)
- If the job continues to fail after all retries, it is moved to the dead letter table
Dead letter jobs require manual administrator intervention — the administrator can inspect the exception message, fix the root cause (e.g., restore a downed service), and replay the job. This can be done through the Flowable Control application.
This automatic retry mechanism is particularly valuable for transient failures like network timeouts, temporary service unavailability, or brief database connection issues.
Exclusive Jobs
The Problem
When multiple async tasks within the same process instance execute concurrently (e.g., after a parallel gateway), they may modify the same process instance data simultaneously. This can cause optimistic locking exceptions at join points (e.g., the closing parallel gateway) because multiple threads try to update the same database row.
The Exclusive Flag
By default, async jobs are exclusive (Exclusive = true). This ensures that only one exclusive job per process instance executes at a time. Other exclusive jobs for the same process instance wait until the current one completes.
This prevents optimistic locking exceptions, but means parallel branches with exclusive async tasks execute sequentially within a single process instance. Jobs from different process instances still run fully in parallel.
When to Disable Exclusive
Set Exclusive = false if you explicitly want parallel execution across branches within the same process instance and can tolerate potential optimistic locking exceptions (which will be automatically retried). Be cautious: retries of non-exclusive jobs can cause duplicate side effects if operations are not idempotent.