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.

Architect's Tech Stack
Architect's Tech Stack
Architect's Tech Stack
Master Flowable BPM Integration with Spring Boot 3: A Step‑by‑Step Guide

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: true

2.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.0

Access 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.

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.

javaworkflowBackend DevelopmentSpring BootBPMFlowable
Architect's Tech Stack
Written by

Architect's Tech Stack

Java backend, microservices, distributed systems, containerized programming, and more.

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.