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.

Code Ape Tech Column
Code Ape Tech Column
Code Ape Tech Column
Master Flowable BPM with Spring Boot 3: Step‑by‑Step Integration 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 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: true

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

Thread pool error illustration
Thread pool error illustration

3. Process Definition

Official Flowable UI Designer

docker run -p 9096:8080 -d --name flow flowable/flowable-ui:6.8.0

Access the UI at http://<host>:9096 (default credentials: admin / test).

Flowable UI login screen
Flowable UI login screen

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.

Designer – start event
Designer – start event
Designer – user task
Designer – user task
Designer – end event
Designer – end event
Final diagram
Final diagram

4. Deploy the Process

4.1 Place the exported XML under src/main/resources

XML placed in resources
XML placed in 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

BPMN‑JS connection line issue
BPMN‑JS connection line issue
Engine‑generated diagram missing nodes
Engine‑generated diagram missing nodes
Engine‑generated diagram with nodes
Engine‑generated diagram with 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.

JavaworkflowSpring BootBPMFlowable
Code Ape Tech Column
Written by

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

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.