Master Flowable BPM Integration with Spring Boot 3: A Step‑by‑Step Guide
This tutorial walks you through integrating the lightweight Flowable BPM engine into a Spring Boot 3.4.1 project, covering dependencies, YAML and thread‑pool configuration, process definition, deployment, querying, task handling, and rejection logic with complete code examples.
1. Flowable Overview
Flowable is a lightweight, open‑source Business Process Management (BPM) and workflow engine that supports the BPMN 2.0 standard and can be used in projects of any size. Core features include process definition, execution, task management, and history queries.
2. Spring Boot 3 Integration
Environment: JDK 21, Spring Boot 3.4.1, Flowable 7.1.0
2.1 Add Dependency
<!-- Flowable starter -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>7.1.0</version>
</dependency>2.2 YAML Configuration
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/flowable?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
flowable:
async-executor-activate: true
database-schema-update: true2.3 Thread‑Pool Configuration
Provide a global default thread pool and a Flowable‑specific pool named applicationTaskExecutor:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
public class ThreadPoolTaskConfig {
@Bean("applicationTaskExecutor")
public ThreadPoolTaskExecutor applicationTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
int core = Runtime.getRuntime().availableProcessors();
executor.setCorePoolSize(core);
executor.setMaxPoolSize(core * 2 + 1);
executor.setKeepAliveSeconds(120);
executor.setQueueCapacity(120);
executor.setThreadNamePrefix("thread-default-execute");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
return executor;
}
}Missing thread‑pool configuration may cause runtime errors (see illustration).
3. Process Definition
Use the official Flowable modeler to design the process.
Run the UI container:
docker run -p 9096:8080 -d --name flow flowable/flowable-ui:6.8.0Access the UI at http://<IP>:9096 (default credentials: admin / test). Create a simple process with a start event, user tasks, and an end event, then connect the nodes.
4. Deploy Process
4.1 Place XML
Copy the exported BPMN XML file into src/main/resources/bpmn/.
4.2 Deploy via Service
@Resource
private RepositoryService repositoryService;
@Resource
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Resource
private HistoryService historyService;
@Resource
private IdentityService identityService;
@GetMapping("initFlow")
@Transactional(rollbackFor = Exception.class)
public void initFlow() {
ClassPathResource bpmnFolder = new ClassPathResource("bpmn/");
var files = bpmnFolder.getFile().listFiles((dir, name) -> name.endsWith(".bpmn20.xml"));
if (files != null && files.length > 0) {
var deploymentBuilder = repositoryService.createDeployment();
for (var file : files) {
deploymentBuilder.addInputStream(file.getName(), file.toURI().toURL().openStream());
}
Deployment deployment = deploymentBuilder.deploy();
}
}5. Query Deployed Processes
@GetMapping("/queryAllDeployedProcesses")
public List<JSONObject> queryAllDeployedProcesses() {
List<JSONObject> jsonObjects = new ArrayList<>();
List<ProcessDefinition> processDefinitions = repositoryService.createProcessDefinitionQuery()
.orderByProcessDefinitionKey().asc()
.latestVersion()
.list();
for (ProcessDefinition pd : processDefinitions) {
JSONObject obj = new JSONObject();
obj.put("id", pd.getId());
obj.put("key", pd.getKey());
obj.put("name", pd.getName());
obj.put("version", pd.getVersion());
jsonObjects.add(obj);
}
return jsonObjects;
}6. Start a Process Instance
@GetMapping("/startFlow")
@Transactional(rollbackFor = Exception.class)
public String startFlow(@RequestParam("key") String key) {
Map<String, Object> vars = Map.of(
"businessType", "业务类型(业务审批、请假、出差等)",
"day", 1,
"refuseFlag", false
);
String userId = SysConstan.USER_ID;
String businessKey = "PO00001";
identityService.setAuthenticatedUserId(userId);
ProcessInstance pi = runtimeService.startProcessInstanceByKey(key, businessKey, vars);
identityService.setAuthenticatedUserId(null);
return pi.getId();
}Key refers to the process definition key obtained after deployment. The refuseFlag variable is used later to indicate rejection.
7. Query All Processes (including history)
@GetMapping("queryAllprocess")
public List<JSONObject> queryAllprocess() {
List<HistoricProcessInstance> instances = historyService.createHistoricProcessInstanceQuery()
.orderByProcessInstanceStartTime().asc()
.list();
List<JSONObject> result = new ArrayList<>();
for (HistoricProcessInstance pi : instances) {
JSONObject json = new JSONObject();
json.put("status", pi.getEndTime() == null ? "审批中" : "审批完成");
json.put("id", pi.getProcessDefinitionId());
json.put("processInstanceId", pi.getId());
json.put("startUser", pi.getStartUserId());
json.put("key", pi.getProcessDefinitionKey());
json.put("businessKey", pi.getBusinessKey());
json.put("name", pi.getProcessDefinitionName());
json.put("deleteReason", pi.getDeleteReason());
json.put("startTime", DateUtil.format(pi.getStartTime(), "yyyy-MM-dd HH:mm:ss"));
json.put("endTime", pi.getEndTime() != null ? DateUtil.format(pi.getEndTime(), "yyyy-MM-dd HH:mm:ss") : "");
List<HistoricVariableInstance> vars = historyService.createHistoricVariableInstanceQuery()
.processInstanceId(pi.getId())
.list();
for (HistoricVariableInstance var : vars) {
json.put(var.getVariableName(), var.getValue());
if ("refuseFlag".equals(var.getVariableName()) && "true".equalsIgnoreCase(String.valueOf(var.getValue()))) {
json.put("status", "审批驳回");
}
}
result.add(json);
}
return result;
}8. Query To‑Do Tasks
@GetMapping("/allTasks")
public List<JSONObject> getTasks() {
List<Task> tasks = taskService.createTaskQuery().list();
List<JSONObject> list = new ArrayList<>();
for (Task t : tasks) {
JSONObject json = new JSONObject();
json.put("id", t.getId());
json.put("name", t.getName());
json.put("user", t.getAssignee());
json.put("processDefinitionId", t.getProcessDefinitionId());
json.put("processInstanceId", t.getProcessInstanceId());
json.putAll(taskService.getVariables(t.getId()));
list.add(json);
}
return list;
}9. Complete a Task
@GetMapping("/testComplete")
@Transactional(rollbackFor = Exception.class)
public boolean testComplete(@RequestParam("id") String taskId) {
Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
if (task == null) {
System.out.println("任务不存在");
return false;
}
String processInstanceId = task.getProcessInstanceId();
taskService.addComment(taskId, processInstanceId, "备注信息test");
taskService.complete(taskId);
return processInstanceFinished(processInstanceId);
}
public boolean processInstanceFinished(String processInstanceId) {
ProcessInstance pi = runtimeService.createProcessInstanceQuery()
.processInstanceId(processInstanceId)
.singleResult();
return pi == null;
}10. Reject a Process
@GetMapping("stopFlow")
@Transactional(rollbackFor = Exception.class)
public void stopFlow(@RequestParam("id") String processInstanceId) {
runtimeService.setVariable(processInstanceId, "refuseFlag", true);
runtimeService.deleteProcessInstance(processInstanceId, "驳回任务备注原因");
}11. Visual Issues
Two common rendering problems are shown: (1) BPMN‑JS connector lines not displayed, and (2) Flowable’s built‑in diagram missing traversed nodes.
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.
Architect's Tech Stack
Java backend, microservices, distributed systems, containerized programming, and more.
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.
