Master Flowable BPM with Spring Boot 3: Step‑by‑Step Integration Guide
This article walks through integrating the open‑source Flowable BPM engine into a Spring Boot 3 application, covering Maven dependencies, YAML datasource and Flowable settings, thread‑pool configuration, process modeling with the Flowable UI, deployment, and APIs for starting, querying, completing, and rejecting workflow instances, all illustrated with code snippets and screenshots.
1. Flowable Overview
Flowable is a lightweight, open‑source Business Process Management (BPM) and workflow engine that supports the BPMN 2.0 standard and is suitable for projects of any size.
2. Spring Boot 3 Integration
Environment: JDK 21, Spring Boot 3.4.1, Flowable 7.1.0.
2.1 Add Maven Dependency
<!-- Flowable startup engine -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>7.1.0</version>
</dependency>2.2 YAML Configuration
spring:
datasource:
# MySQL configuration
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:
# Enable async job executor
async-executor-activate: true
# Auto‑update database schema on startup
database-schema-update: true2.3 Thread‑Pool Configuration
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 image).
3. Process Definition
Official Flowable UI Designer
docker run -p 9096:8080 -d --name flow flowable/flowable-ui:6.8.0Access the UI at http://<host>:9096 (default credentials: admin / test).
Steps to create a simple approval process:
Enter the designer.
Create a start event, user task, and end event.
Assign a fixed user (e.g., user1) to the user task.
Connect the nodes.
4. Deploy the Process
4.1 Place the exported XML under src/main/resources
4.2 Deploy via RepositoryService
@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) {
System.out.println("Process ID: " + pd.getId());
System.out.println("Process Key: " + pd.getKey());
System.out.println("Process Name: " + pd.getName());
System.out.println("Process Version: " + pd.getVersion());
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 an Approval Process
@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; // authenticated user
String businessKey = "PO00001"; // order number
identityService.setAuthenticatedUserId(userId);
ProcessInstance pi = runtimeService.startProcessInstanceByKey(key, businessKey, vars);
log.info("流程实例id-{}", pi.getId());
identityService.setAuthenticatedUserId(null);
return pi.getId();
}Key points: key is the process definition key obtained after deployment; businessKey represents the business order number; the variable map can be extended for custom data.
7. Query All Processes and Their Status
@GetMapping("/queryAllprocess")
public List<JSONObject> queryAllprocess() {
List<HistoricProcessInstance> all = historyService.createHistoricProcessInstanceQuery()
.orderByProcessInstanceStartTime().asc()
.list();
List<JSONObject> result = new ArrayList<>();
for (HistoricProcessInstance pi : all) {
JSONObject json = new JSONObject();
if (pi.getEndTime() == null) {
json.put("status", "审批中");
} else {
json.put("status", "审批完成");
}
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") : "");
// variables
List<HistoricVariableInstance> vars = historyService.createHistoricVariableInstanceQuery()
.processInstanceId(pi.getId()).list();
for (HistoricVariableInstance v : vars) {
json.put(v.getVariableName(), v.getValue());
if (REFUSEFLAG.equals(v.getVariableName()) && v.getValue() != null && "true".equalsIgnoreCase(v.getValue().toString())) {
json.put("status", "审批驳回");
}
}
result.add(json);
}
return result;
}8. My Tasks (Todo List)
@GetMapping("/allTasks")
public List<JSONObject> getTasks() {
List<Task> tasks = taskService.createTaskQuery().list();
List<JSONObject> result = 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());
Map<String, Object> vars = taskService.getVariables(t.getId());
json.putAll(vars);
result.add(json);
}
return result;
}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();
String orderCode = (String) runtimeService.getVariable(processInstanceId, "orderCode");
log.info("业务单据:{}", orderCode);
taskService.addComment(taskId, processInstanceId, "备注信息test");
taskService.complete(taskId);
boolean isFinish = processInstanceFinished(processInstanceId);
log.debug("流程是否完成L:{}", isFinish);
return isFinish;
}
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. Final Effect Screenshots
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.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
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.
