How to Integrate Flowable BPM with Spring Boot 3: A Step‑by‑Step Guide

This article provides a comprehensive, code‑driven tutorial on integrating the Flowable BPM engine with Spring Boot 3, covering dependency setup, YAML configuration, thread‑pool tuning, process definition using the Flowable UI, deployment, starting processes, querying deployments and instances, handling tasks, completing and rejecting workflows, all illustrated with practical examples and screenshots.

Top Architect
Top Architect
Top Architect
How to Integrate Flowable BPM with Spring Boot 3: A Step‑by‑Step Guide

1. Flowable Introduction

Flowable is a lightweight open‑source BPM and workflow engine that supports BPMN 2.0. It provides process definition, execution, task management and history query.

2. Spring Boot 3 Integration

Environment: JDK 21, Spring Boot 3.4.1, Flowable 7.1.0.

1. Add Dependency

<!-- Flowable starter dependency -->
<dependency>
  <groupId>org.flowable</groupId>
  <artifactId>flowable-spring-boot-starter</artifactId>
  <version>7.1.0</version>
</dependency>

2. yml 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: true

3. Thread‑Pool Configuration

@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;
  }
}

3. Process Definition

Use the Flowable UI (docker run -p 9096:8080 -d --name flow flowable/flowable-ui:6.8.0) to design a process, set user tasks, assign a fixed user (user1), and connect the nodes.

4. Deploy Process

Place the exported XML file in the resources directory and deploy it programmatically.

@Resource
private RepositoryService repositoryService;
...
public void initFlow() {
  ClassPathResource bpmnFolder = new ClassPathResource("bpmn/");
  File[] files = bpmnFolder.getFile().listFiles((dir, name) -> name.endsWith(".bpmn20.xml"));
  if (files != null && files.length > 0) {
    DeploymentBuilder deploymentBuilder = repositoryService.createDeployment();
    for (File file : files) {
      deploymentBuilder.addInputStream(file.getName(), file.toURI().toURL().openStream());
    }
    deploymentBuilder.deploy();
  }
}

5. Start Process

@GetMapping("/startFlow")
@Transactional
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();
}

6. Query Deployed Processes

@GetMapping("/queryAllDeployedProcesses")
public List<JSONObject> queryAllDeployedProcesses() {
  List<ProcessDefinition> defs = repositoryService.createProcessDefinitionQuery()
      .orderByProcessDefinitionKey().asc()
      .latestVersion()
      .list();
  List<JSONObject> result = new ArrayList<>();
  for (ProcessDefinition d : defs) {
    JSONObject o = new JSONObject();
    o.put("id", d.getId());
    o.put("key", d.getKey());
    o.put("name", d.getName());
    o.put("version", d.getVersion());
    result.add(o);
  }
  return result;
}

7. Query All Process Instances

@GetMapping("queryAllprocess")
public List<JSONObject> queryAllprocess() {
  List<HistoricProcessInstance> instances = historyService.createHistoricProcessInstanceQuery()
      .orderByProcessInstanceStartTime().asc()
      .list();
  List<JSONObject> list = 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 v : vars) {
      json.put(v.getVariableName(), v.getValue());
      if ("refuseFlag".equals(v.getVariableName()) && Boolean.TRUE.equals(v.getValue())) {
        json.put("status", "审批驳回");
      }
    }
    list.add(json);
  }
  return list;
}

8. My Tasks

@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());
    json.putAll(taskService.getVariables(t.getId()));
    result.add(json);
  }
  return result;
}

9. Complete Task

@GetMapping("/testComplete")
@Transactional
public boolean testComplete(@RequestParam("id") String taskId) {
  Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
  if (task == null) {
    return false;
  }
  String processInstanceId = task.getProcessInstanceId();
  taskService.addComment(taskId, processInstanceId, "备注信息test");
  taskService.complete(taskId);
  return processInstanceFinished(processInstanceId);
}

10. Reject Process

@GetMapping("stopFlow")
@Transactional
public void stopFlow(@RequestParam("id") String processInstanceId) {
  runtimeService.setVariable(processInstanceId, "refuseFlag", true);
  runtimeService.deleteProcessInstance(processInstanceId, "驳回任务备注原因");
}
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

workflowSpring BootBPMFlowable
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.