Master Flowable BPM: Integrate with Spring Boot 3 for Seamless Workflow Automation

This tutorial walks through Flowable BPM basics, Spring Boot 3 integration, Maven dependencies, YAML configuration, thread‑pool setup, process definition via the Flowable UI, deployment, querying, starting, task handling, completion, and rejection, providing complete code snippets and screenshots for a functional workflow system.

Java Tech Enthusiast
Java Tech Enthusiast
Java Tech Enthusiast
Master Flowable BPM: Integrate with Spring Boot 3 for Seamless Workflow Automation

Flowable Overview

Flowable is a lightweight open‑source BPM and workflow engine that supports BPMN 2.0, providing features such as process definition, execution, task management and history queries, suitable for enterprise‑level applications.

Official site: https://www.flowable.com/open-source/docs/

Project example based on Spring Boot 3, MyBatis‑Plus, Vue & Element includes RBAC, multi‑tenant, data permission, workflow, third‑party login, payment, SMS, and e‑commerce functions.

Project address: https://github.com/YunaiV/ruoyi-vue-pro

Video tutorial: https://doc.iocoder.cn/video/

Spring Boot 3 Integration

Environment: JDK 21, Spring Boot 3.4.1, Flowable 7.1.0

1. Add Maven Dependency

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

2. yml Configuration

Data source uses MySQL and Druid; adjust according to your environment.

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

Provide a global default thread pool and a Flowable‑specific pool named applicationTaskExecutor.

@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 errors.

Process Definition

Use the official Flowable UI designer (docker run ...) to create and edit BPMN diagrams.

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

Access the UI at the container’s IP and port; default credentials admin/test. Create a process, add user tasks, set assignee (e.g., user1), connect nodes, and export the XML.

Deploy Process

Place the exported XML under resources and deploy it programmatically.

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

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

Start a 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;
    String businessKey = "PO00001";
    identityService.setAuthenticatedUserId(userId);
    ProcessInstance pi = runtimeService.startProcessInstanceByKey(key, businessKey, vars);
    log.info("流程实例id-{}", pi.getId());
    identityService.setAuthenticatedUserId(null);
    return pi.getId();
}

Key refers to the process definition key obtained after deployment. The variable map can be extended to control branching (e.g., leave days).

Query All Processes

@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();
        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()) && Boolean.TRUE.equals(var.getValue())) {
                json.put("status", "审批驳回");
            }
        }
        result.add(json);
    }
    return result;
}

My Tasks

@GetMapping("/allTasks")
public List<JSONObject> getTasks() {
    List<Task> tasks = taskService.createTaskQuery().list();
    List<JSONObject> result = new ArrayList<>();
    for (Task task : tasks) {
        JSONObject json = new JSONObject();
        json.put("id", task.getId());
        json.put("name", task.getName());
        json.put("user", task.getAssignee());
        json.put("processDefinitionId", task.getProcessDefinitionId());
        json.put("processInstanceId", task.getProcessInstanceId());
        json.putAll(taskService.getVariables(task.getId()));
        result.add(json);
    }
    return result;
}

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);
    boolean isFinish = processInstanceFinished(processInstanceId);
    return isFinish;
}

public boolean processInstanceFinished(String processInstanceId) {
    ProcessInstance pi = runtimeService.createProcessInstanceQuery()
        .processInstanceId(processInstanceId)
        .singleResult();
    return pi == null;
}

Reject a Process

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

The article concludes with screenshots of the final UI and notes about rendering issues with BPMN‑JS and Flowable’s built‑in diagram generator.

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.

BackendworkflowSpring BootBPMFlowable
Java Tech Enthusiast
Written by

Java Tech Enthusiast

Sharing computer programming language knowledge, focusing on Java fundamentals, data structures, related tools, Spring Cloud, IntelliJ IDEA... Book giveaways, red‑packet rewards and other perks await!

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.