Integrating Flowable Workflow Engine with Spring Boot: A Step‑by‑Step Guide
This article introduces the evolution of Java‑based workflow engines, compares Activiti, Flowable and Camunda, explains core BPMN concepts, and provides a detailed Spring Boot integration tutorial—including Maven dependencies, BPMN diagram creation, service task implementation, REST endpoint for diagram viewing, unit tests, and common troubleshooting tips.
1. Workflow Engine Overview
Early workflow engines such as jBPM were Java‑based enterprise process engines developed by JBoss; its creator Tom Baeyens later founded Activiti. Diverging opinions split jBPM into Activiti and a separate jBPM project, and Activiti later spawned Camunda and Flowable.
Three mainstream engines today:
Activiti – now cloud‑focused, aligning with Spring Cloud and Docker.
Flowable – a feature‑rich engine offering many extension points.
Camunda – lightweight with a built‑in BPMN editor (bpmn.io) for embedded, customizable workflows.
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)
Sequence Flows (connections, optionally with conditions)
Tasks (receive, send, service, script, user)
Gateways (exclusive, parallel, event, inclusive)
Events
Start and end events are mandatory; additional intermediate or boundary events can be added.
Sequence Flows
Connect events, tasks and gateways; conditional flows use expressions such as ${var:equals(checkResult,"通过")} .
Tasks
Receive Task – pauses the process awaiting manual input.
Send Task – sends messages to external participants.
Service Task – executes Java code or remote services.
Script Task – runs inline scripts automatically.
User Task – requires human interaction.
Gateways
Exclusive (XOR) Gateway – only one outgoing path is taken.
Parallel Gateway – splits or joins parallel flows.
Event Gateway – waits for an event before proceeding.
Inclusive Gateway – can take multiple outgoing paths.
3. Spring Boot Integration with Flowable
3.1 Project Setup and Dependencies
Create a Spring Boot project (IDEA) and add MySQL & MyBatis dependencies because Flowable needs a database.
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring-boot-starter</artifactId>
<version>6.7.2</version>
</dependency>Process files placed under resources/processes are auto‑deployed.
3.2 Drawing the BPMN Diagram
Use the Flowable BPMN visualizer plugin or IDEA plugin to create ask_for_leave.bpmn20.xml . The diagram links events, user tasks (employee, leader, manager), exclusive gateways for approval decisions, and a service task for failure notifications.
3.3 Service Task Implementation
@Slf4j
@Component
public class LeaveFailService implements JavaDelegate {
@Override
public void execute(DelegateExecution delegateExecution) {
log.info("审批不通过{}", delegateExecution.getCurrentActivityId());
}
}The class implements JavaDelegate and runs custom logic when the service task is triggered.
3.4 REST Endpoint for Diagram Viewing
@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
executions = runtimeService.createExecutionQuery()
.processInstanceId(processId).list();
List
activityIds = new ArrayList<>();
for (Execution exe : executions) {
activityIds.addAll(runtimeService.getActiveActivityIds(exe.getId()));
}
BpmnModel bpmnModel = repositoryService.getBpmnModel(pi.getProcessDefinitionId());
ProcessEngineConfiguration engconf = processEngine.getProcessEngineConfiguration();
ProcessDiagramGenerator diagramGenerator = engconf.getProcessDiagramGenerator();
InputStream in = diagramGenerator.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 generates a PNG of the current process state.
3.5 Unit Tests
@SpringBootTest
@Slf4j
@ActiveProfiles("dev")
class ProcessApplicationTests {
@Autowired RuntimeService runtimeService;
@Autowired TaskService taskService;
public static final String yuangongId = "yuangongID_3";
public static final String zuzhangId = "zuzhangId_3";
public static final String manageId = "manageId_3";
@Test
void askForLeave() {
HashMap
map = new HashMap<>();
map.put("leaveTask", yuangongId);
ProcessInstance pi = runtimeService.startProcessInstanceByKey("ask_for_leave", map);
runtimeService.setVariable(pi.getId(), "name", "javaboy");
runtimeService.setVariable(pi.getId(), "reason", "休息一下");
runtimeService.setVariable(pi.getId(), "days", 10);
log.info("创建请假流程 processId:{}", pi.getId());
}
@Test
void submitToZuZhang() {
List
list = taskService.createTaskQuery().taskAssignee(yuangongId).list();
for (Task task : list) {
Map
map = new HashMap<>();
map.put("zuzhangTask", zuzhangId);
taskService.complete(task.getId(), map);
}
}
@Test
void zuZhangApprove() {
List
list = taskService.createTaskQuery().taskAssignee(zuzhangId).list();
for (Task task : list) {
if ("组长审批".equals(task.getName())) {
Map
map = new HashMap<>();
map.put("manageTask", manageId);
map.put("checkResult", "通过");
map.put("zuzhangTask", zuzhangId);
taskService.complete(task.getId(), map);
}
}
}
@Test
void managerApprove() {
List
list = taskService.createTaskQuery().taskAssignee(manageId).list();
for (Task task : list) {
Map
map = new HashMap<>();
map.put("checkResult", "通过");
taskService.complete(task.getId(), map);
}
}
@Test
void managerNotApprove() {
List
list = taskService.createTaskQuery().taskAssignee(manageId).list();
for (Task task : list) {
Map
map = new HashMap<>();
map.put("checkResult", "拒绝");
taskService.complete(task.getId(), map);
}
}
}The tests cover process start, employee submission, leader approval, manager approval, and rejection scenarios.
4. Common Issues
4.1 Diagram Text Appears as "口口口"
This occurs when the host lacks a default font. Install a Chinese font (e.g., SimSun) or configure Flowable to use it:
@Configuration
public class FlowableConfig implements EngineConfigurationConfigurer
{
@Override
public void configure(SpringProcessEngineConfiguration cfg) {
cfg.setActivityFontName("宋体");
cfg.setLabelFontName("宋体");
cfg.setAnnotationFontName("宋体");
}
}4.2 Modifying a Process Definition After an Instance Has Started
Changes only affect new instances; already running instances keep the original persisted definition.
Author: 人生漫漫唯有奋斗
Final note: If you find this tutorial helpful, please like, share, and follow the author.
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
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.