Spring Boot + Flowable 7.x: Enterprise-Grade Integration Guide with Production Pitfalls
A comprehensive practical guide to integrating Flowable 7.x with Spring Boot for enterprise workflow systems, covering core concepts, database initialization strategies, process versioning, dynamic form decoupling, multi-instance approval patterns, listener injection, historical data archiving, performance tuning, cluster deployment, multi-tenancy, and production-grade transaction and deadlock handling.
1. Core Concepts: Understand Flowable as a State Machine
Flowable implements BPMN 2.0. Instead of memorizing XML tags, treat it as a state machine with business logic:
Process Definition : Equivalent to a Java Class — the parsed BPMN XML metadata.
Process Instance : Equivalent to an Object — a concrete execution of a definition.
Execution (Token) : Critical concept. In a single-path flow, 1:1 with instance. At parallel gateways, tokens split into multiple Executions.
Task : UserTask (human) and ServiceTask (machine).
Variable : The "blood" of flow — carries data between nodes and drives gateway decisions.
BPMN core elements reduce to three categories: Events (start, end, timer), Tasks, Gateways (exclusive, parallel, inclusive). Use Flowable's modeler or IDEA plugin to drag-and-drop instead of hand-writing XML.
2. Integration & Database Initialization: The First Production Gate
Add the starter dependency (Flowable 7.x):
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter-process</artifactId>
<version>7.0.0</version>
</dependency>Database schema update strategy via flowable.database-schema-update: true: Check schema at startup, create if missing, upgrade if version mismatch. false: No DDL; error if tables missing. create-drop / drop-create: Create on start/drop on shutdown, or drop then create.
Veteran advice : Use true locally. Never use true in production . Let the framework auto-alter live tables is suicidal. Correct approach: extract Flowable DDL scripts, manage via Flyway or Liquibase, then set config to false.
3. Process Deployment & Version Control: Old Instances Stay on Old Versions
Versioning revolves around KEY and VERSION . Deploy .bpmn20.xml under classpath:processes/ for auto-deploy, or use RepositoryService API for dynamic XML deployment.
On deploy, engine checks for existing KEY. If found, VERSION increments; else starts at 1.
Critical pitfall : New version only affects newly started instances. Running instances cling to their original version. Forcing migration via ProcessInstanceMigrationValidator risks corrupting live data. Standard practice: let old instances finish on old version; new instances use new version.
4. Dynamic Forms & Variables: Decouple Completely from Built-in Form Engine
Rule #1: Never use Flowable's Form Engine — too primitive for enterprise dynamic forms.
Standard pattern: Total decoupling of business forms from the engine .
Form data lives in your business tables (e.g., leave_request).
On process start, push only "business primary key" and "core routing indicators" into process variables.
Map<String, Object> variables = new HashMap<>();
variables.put("businessKey", "LEAVE-20231024-001"); // business PK for reverse lookup
variables.put("days", 5); // core routing indicator: leave days
runtimeService.startProcessInstanceByKey("leave_process", variables);Gateway routing : Write UEL expressions on exclusive gateway sequence flows:
Flow 1 (Manager): ${days <= 3} Flow 2 (Director): ${days > 3 && days <= 7} Flow 3 (CEO): ${days > 7} Must configure a Default Flow on exclusive gateway. If dirty data causes no condition to match, the process stalls — you'll be woken at 3 AM to fix it.
5. Multi-Instance Approval & Rejection Logic
5.1 Multi-Instance Configuration
On a UserTask, configure multi-instance for parallel or sequential approval:
Collection variable: assigneeList (e.g., ["zhangsan", "lisi"])
Element variable: currentAssignee (current loop approver) isSequential: false = parallel, true = sequential
5.2 Completion Condition
Use completionCondition to decide when multi-instance ends:
Unanimous (all must approve) : ${nrOfCompletedInstances == nrOfInstances} Any one approves : ${nrOfCompletedInstances > 0} Percentage threshold : ${nrOfCompletedInstances / nrOfInstances >= 0.5} Note : nrOfCompletedInstances, nrOfInstances are engine-provided local variables.
5.3 Rejection & Withdrawal
BPMN has no "rejection" concept — it's essentially node jumping . Since 6.4, ChangeActivityStateBuilder is provided:
// Rejection: force-jump current task node to a historical target node
processRuntime.changeActivityState(
ChangeActivityStateBuilder.builder()
.processInstanceId(processInstanceId)
.moveSingleExecutionToActivityIds(currentExecutionId, targetActivityId)
.build()
);"Withdrawal" = approver just clicked agree, next assignee hasn't acted. Query current task, move execution token back to original node, clean up extra variables.
6. Listeners: Must Use Spring Bean Injection
Listeners are the decoupling lever.
Execution Listener : Bound to sequence flows, events, tasks. Triggers start, end, take. Used for audit logging.
Task Listener : Only on UserTask. Triggers create, assignment, complete, delete. Used for dynamic assignee calculation.
Painful lesson : Never hardcode Java class FQCN in BPMN XML ( class="com.xxx.MyListener"). If listener needs a Service, you'd have to fish it from Spring container manually — painful. Use delegateExpression to inject Spring Beans:
@Component("dynamicAssigneeListener")
public class DynamicAssigneeListener implements TaskListener {
@Autowired
private UserService userService;
@Override
public void notify(DelegateTask delegateTask) {
String deptId = (String) delegateTask.getVariable("deptId");
String leaderId = userService.getDeptLeader(deptId);
delegateTask.setAssignee(leaderId);
}
}XML config:
<flowable:taskListener event="create" delegateExpression="${dynamicAssigneeListener}" />. Clean and simple.
7. Historical Data Archiving: Flowable's Biggest Pain Point
ACT_HI_*tables are a bottomless pit. Data grows unbounded; query performance collapses. Production must implement cold/hot separation .
Suspend : Freeze instance; tasks cannot proceed. For voided documents needing audit trail.
Terminate (Delete) : Physically delete runtime data ( ACT_RU_*), record deletion reason in history.
Archival strategy :
Hot data (runtime + last 3 months history) stays in MySQL.
Cold data (completed > 3 months ago): scheduled job (e.g., XXL-JOB) runs daily at midnight. Query ACT_HI_PROCINST for END_TIME_ older than 3 months.
Batch INSERT corresponding history rows into archive store (ClickHouse or separate MySQL archive DB). DELETE from primary DB.
Critical : Archival must use distributed lock and batch process (1000 rows per batch). A single DELETE of hundreds of thousands of rows will lock the database solid.
8. Performance Tuning & Cluster Deployment
8.1 Performance Tuning
Async continuation : ServiceTask with complex logic or external calls must enable Asynchronous Continuations in BPMN. Engine converts to Job, executes in background thread pool; otherwise long transactions exhaust DB connection pool.
History level : Options: none, activity, audit, full. Production uses audit (default) — records instances, tasks, activities. Never use full — logs every variable change, kills performance. Config: flowable.history-level: audit.
Connection pool : Use HikariCP. Pool size 50-100 typically sufficient; engine is mixed IO+CPU intensive.
8.2 Cluster Deployment
Flowable clusters natively via database-level distributed locks . All nodes share one DB. Set flowable.async-executor-activate=true. Async Executor contends for ACT_RU_JOB records using SELECT ... FOR UPDATE ensuring each Job runs on only one node. If DB lock becomes bottleneck at extreme concurrency, replace Job lock with Redis (Redisson) — but 90% of companies never hit this limit.
9. Permissions & Multi-Tenancy: Don't Use Engine's Built-in User System
Again: Abandon Flowable's IdentityService ! Your Spring Security + JWT RBAC already exists; don't maintain a second user model in the engine.
Query pending tasks using Spring Security context user ID:
taskService.createTaskQuery()
.taskAssignee(SecurityUtils.getCurrentUserId())
.orderByTaskCreateTime().desc()
.list();For SaaS multi-tenancy, Flowable supports tenantId natively. Deploy with tenant ID; always add .processInstanceTenantId("tenant_001") on queries and advances . For large data, use MyBatis interceptor to append tenant predicate at SQL layer, preventing privilege escalation.
10. Production-Grade Pitfalls: Transactions, Deadlocks, Exceptions
10.1 Transaction Boundary Conflicts
Flowable APIs use Command pattern wrapped in its own transaction interceptor. If a ServiceTask calls a slow RPC, Flowable's DB transaction stays open too long.
Fix : Make long operations async, or have ServiceTask only publish an MQ message; let consumer do the heavy lifting.
10.2 Deadlock Prevention
In a single @Transactional method, if Process A triggers Process B, and B updates A's variables, deadlock is highly probable.
Fix : Don't nest multiple process-advance APIs in one transaction. Decouple with Spring @Async + @EventListener or MQ.
10.3 Exception Compensation
ServiceTask calls external system (e.g., inventory deduction) fails. Don't naively throw exception to rollback — external system may have partially succeeded.
Real-world approach : Don't over-rely on BPMN compensation events (too complex). In JavaDelegate, try-catch, stuff error code into process variable (e.g., errorCode = "INSUFFICIENT_BALANCE"), let flow proceed to next exclusive gateway, route by error code to "exception handling node" or "manual intervention node". Simple, brutal, easy to debug.
Workflow engines: easy to start, hard to master. One core principle: Treat the engine as a pure state machine . Strip out business logic, permissions, long-running ops. It's an engine, not the business system itself. Clarify this boundary and Flowable delivers real power.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Xiaolin Talks Programming
Focuses on sharing original technical insights. Senior architect at a top tech company with years of experience in technical architecture and management, and extensive interview experience. Offers one-on-one technical coaching, guiding you from beginner to architecture design to technical management. Follow for free learning resources. Free one-on-one interview coaching to help you land offers quickly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
