Step‑by‑Step Guide: Integrating Flowable BPMN with Spring Boot for Leave Approval Workflow

This article walks through the evolution of Java‑based workflow engines, compares Activiti, Flowable and Camunda, and provides a complete Spring Boot integration tutorial—including Maven setup, BPMN modeling, service task implementation, diagram generation, unit testing and common troubleshooting—using a concrete leave‑request example.

Architect
Architect
Architect
Step‑by‑Step Guide: Integrating Flowable BPMN with Spring Boot for Leave Approval Workflow

1. Workflow Engine Overview

Early Java workflow engines started with jBPM, created by Tom Baeyens at JBoss. After Baeyens left JBoss for Alfresco, he launched Activiti based on jBPM 4, while the original jBPM project abandoned the jBPM 4 code. This split later produced three major engines:

Activiti – now oriented toward cloud environments and integrates with Spring Cloud and Docker.

Flowable – focuses on a feature‑rich engine with many extension points, allowing developers to build custom functionality on top of the core engine.

Camunda – lightweight, offering a compact BPMN editor built on bpmn.io, ideal for embedded, highly customizable editors.

2. Core BPMN Concepts

Using a simple leave‑request process as an example, a BPMN diagram consists of four elements:

Events – start, end, intermediate, boundary, etc.

Sequence Flows – connections between elements, optionally guarded by condition expressions.

Tasks – user tasks, service tasks, script tasks, send tasks, receive tasks, etc.

Gateways – exclusive (XOR), inclusive (OR), parallel (AND), event‑based, etc.

Task Types Explained

User Task : waits for a human to complete the step (e.g., employee submits a leave request).

Service Task : executes Java code or calls a remote service automatically.

Script Task : runs an inline script when the flow reaches the node.

Send Task : sends a message to an external participant.

Gateway Types Explained

Exclusive Gateway (互斥网关): only one outgoing path is taken based on a condition.

Inclusive Gateway (相容网关): multiple outgoing paths may be taken if their conditions are satisfied.

Event Gateway : waits for a specific event before proceeding.

Parallel Gateway : splits or joins parallel branches.

3. Integrating Flowable with Spring Boot

3.1 Create a Spring Boot project and add dependencies

<dependency>
    <groupId>org.flowable</groupId>
    <artifactId>flowable-spring-boot-starter</artifactId>
    <version>6.7.2</version>
</dependency>

Because Flowable persists process state, add MySQL and MyBatis dependencies as well.

3.2 BPMN file placement

All BPMN files placed under src/main/resources/processes are automatically deployed at startup.

3.3 Example BPMN definition (ask_for_leave.bpmn20.xml)

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:flowable="http://flowable.org/bpmn"
    targetNamespace="http://www.flowable.org/processdef">
  <process id="ask_for_leave" name="ask_for_leave" isExecutable="true">
    <startEvent id="start"/>
    <userTask id="employeeTask" name="请假" flowable:assignee="#{leaveTask}">
      <documentation>员工请假</documentation>
    </userTask>
    <sequenceFlow sourceRef="start" targetRef="employeeTask"/>
    <userTask id="leaderTask" name="组长审批" flowable:assignee="#{zuzhangTask}"/>
    <sequenceFlow sourceRef="employeeTask" targetRef="leaderTask"/>
    <exclusiveGateway id="leaderGateway" name="组长审批网关"/>
    <sequenceFlow sourceRef="leaderTask" targetRef="leaderGateway"/>
    <sequenceFlow id="leaderApprove" sourceRef="leaderGateway" targetRef="managerTask" name="组长审批通过">
      <conditionExpression xsi:type="tFormalExpression">${var:equals(checkResult,"通过")}</conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="leaderReject" sourceRef="leaderGateway" targetRef="failService" name="组长审批拒绝">
      <conditionExpression xsi:type="tFormalExpression">${var:equals(checkResult,"拒绝")}</conditionExpression>
    </sequenceFlow>
    <serviceTask id="failService" name="发送失败提示" flowable:class="com.xx.process.service.LeaveFailService" flowable:exclusive="true"/>
    <userTask id="managerTask" name="经理审批" flowable:assignee="#{manageTask}"/>
    <exclusiveGateway id="managerGateway" name="经理审批网关"/>
    <sequenceFlow sourceRef="managerTask" targetRef="managerGateway"/>
    <...additional flows for manager approve/reject...>
    <endEvent id="end"/>
  </process>
</definitions>

3.4 Service task implementation

@Slf4j
@Component
public class LeaveFailService implements JavaDelegate {
    @Override
    public void execute(DelegateExecution delegateExecution) {
        log.info("审批不通过 {}", delegateExecution.getCurrentActivityId());
    }
}

The class implements JavaDelegate; Flowable invokes execute automatically when the service task is reached.

3.5 API to view the current diagram

@RestController
public class LeaveController {
    @Autowired RuntimeService runtimeService;
    @Autowired RepositoryService repositoryService;
    @Autowired ProcessEngine processEngine;

    @GetMapping("/pic")
    public void showPic(HttpServletResponse resp, String processId) throws Exception {
        ProcessInstance pi = runtimeService.createProcessInstanceQuery()
                .processInstanceId(processId).singleResult();
        if (pi == null) return;
        List<Execution> executions = runtimeService.createExecutionQuery()
                .processInstanceId(processId).list();
        List<String> activityIds = new ArrayList<>();
        for (Execution exe : executions) {
            activityIds.addAll(runtimeService.getActiveActivityIds(exe.getId()));
        }
        BpmnModel bpmnModel = repositoryService.getBpmnModel(pi.getProcessDefinitionId());
        ProcessEngineConfiguration engConf = processEngine.getProcessEngineConfiguration();
        ProcessDiagramGenerator generator = engConf.getProcessDiagramGenerator();
        InputStream in = generator.generateDiagram(bpmnModel, "png", activityIds, new ArrayList<>(),
                engConf.getActivityFontName(), engConf.getLabelFontName(), engConf.getAnnotationFontName(),
                engConf.getClassLoader(), 1.0, false);
        OutputStream out = resp.getOutputStream();
        byte[] buf = new byte[1024];
        int len;
        while ((len = in.read(buf)) != -1) {
            out.write(buf, 0, len);
        }
        in.close();
        out.close();
    }
}

The endpoint receives a process instance ID and returns a PNG showing the current active nodes.

3.6 Unit tests covering the whole flow

@SpringBootTest
@Slf4j
@ActiveProfiles("dev")
class ProcessApplicationTests {
    @Autowired RuntimeService runtimeService;
    @Autowired TaskService taskService;

    static final String employeeId = "yuangongID_3";
    static final String leaderId   = "zuzhangId_3";
    static final String managerId  = "manageId_3";

    @Test
    void askForLeave() {
        Map<String, Object> vars = new HashMap<>();
        vars.put("leaveTask", employeeId);
        ProcessInstance pi = runtimeService.startProcessInstanceByKey("ask_for_leave", vars);
        runtimeService.setVariable(pi.getId(), "name", "javaboy");
        runtimeService.setVariable(pi.getId(), "reason", "休息一下");
        runtimeService.setVariable(pi.getId(), "days", 10);
        log.info("创建请假流程 processId:{}", pi.getId());
    }

    @Test
    void submitToLeader() {
        List<Task> tasks = taskService.createTaskQuery().taskAssignee(employeeId).list();
        for (Task task : tasks) {
            Map<String, Object> map = new HashMap<>();
            map.put("zuzhangTask", leaderId);
            taskService.complete(task.getId(), map);
        }
    }

    @Test
    void leaderApprove() {
        List<Task> tasks = taskService.createTaskQuery().taskAssignee(leaderId).list();
        for (Task task : tasks) {
            if ("组长审批".equals(task.getName())) {
                Map<String, Object> map = new HashMap<>();
                map.put("manageTask", managerId);
                map.put("checkResult", "通过");
                map.put("zuzhangTask", leaderId);
                taskService.complete(task.getId(), map);
            }
        }
    }

    @Test
    void managerApprove() {
        List<Task> tasks = taskService.createTaskQuery().taskAssignee(managerId).list();
        for (Task task : tasks) {
            Map<String, Object> map = new HashMap<>();
            map.put("checkResult", "通过");
            taskService.complete(task.getId(), map);
        }
    }

    @Test
    void managerReject() {
        List<Task> tasks = taskService.createTaskQuery().taskAssignee(managerId).list();
        for (Task task : tasks) {
            Map<String, Object> map = new HashMap<>();
            map.put("checkResult", "拒绝");
            taskService.complete(task.getId(), map);
        }
    }
}

The tests simulate the full lifecycle: start the process, employee submits, leader approves, manager approves or rejects.

3.7 Common pitfalls

Diagram text appears as garbled characters : caused by missing default fonts on the host machine. Adding a Chinese font (e.g., "宋体") or configuring the engine font names resolves the issue.

Changing a BPMN file does not affect already‑running instances : once a process instance starts, its definition is persisted. Modifications only affect new instances; existing ones continue with the original definition.

4. Configuration to fix missing fonts

@Configuration
public class FlowableConfig implements EngineConfigurationConfigurer<SpringProcessEngineConfiguration> {
    @Override
    public void configure(SpringProcessEngineConfiguration cfg) {
        cfg.setActivityFontName("宋体");
        cfg.setLabelFontName("宋体");
        cfg.setAnnotationFontName("宋体");
    }
}

Inject this configuration bean so that Flowable uses the specified font when generating PNG diagrams.

By following the steps above, developers can quickly set up a Flowable‑based leave‑approval workflow, understand each BPMN element, customize service tasks, visualize runtime state, and write automated tests to guarantee correctness.

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.

JavaworkflowBPMNSpring BootProcess EngineFlowable
Architect
Written by

Architect

Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.

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.