Choosing the Right Workflow Engine: Activiti, Flowable, Camunda, jBPM & JDEasyFlow
This article explains what workflow engines are, compares five popular options—Activiti, Flowable, Camunda, jBPM and JDEasyFlow—detailing their core principles, architecture, code examples, and suitable scenarios, and provides a comprehensive table and decision flowchart to help developers select the most appropriate engine for their projects.
Workflow engines are the core support systems for business process automation, coordinating the sequence of business nodes much like traffic signals manage vehicle flow.
What Is a Workflow Engine?
A workflow engine abstracts complex business logic into discrete tasks and controls the flow between them, allowing visual process definition and decoupling business logic from flow control.
Typical benefits include reduced complexity, improved maintainability, enhanced visualization, and guaranteed consistency through state machines.
Activiti: Enterprise‑Grade Standard Workflow Engine
Core Principles
Activiti is based on the BPMN 2.0 standard and uses a state‑machine plus command‑pattern design. It models processes as a series of activity nodes with tokens moving between them.
Architecture Features
Full BPMN 2.0 element support
Command pattern encapsulates all operations
State‑machine manages process instance state
Transactional process execution
Open‑Source Repository
GitHub: https://github.com/Activiti/Activiti
Example: Leave Approval Process
Dependency for Spring Boot:
<dependency>
<groupId>org.activiti</groupId>
<artifactId>activiti-spring-boot-starter</artifactId>
<version>7.1.0.M6</version>
</dependency>BPMN definition (leave‑approval.bpmn20.xml):
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL">
<process id="leaveApproval" name="请假审批流程" isExecutable="true">
<!-- Start Event -->
<startEvent id="startEvent"/>
<!-- User submits application -->
<userTask id="submitLeave" name="提交请假申请" activiti:assignee="#{employee}"/>
<!-- Supervisor approval -->
<userTask id="leaderApproval" name="直接主管审批" activiti:assignee="#{leader}"/>
<!-- Decision gateway -->
<exclusiveGateway id="decisionGateway"/>
<!-- Approved flow -->
<sequenceFlow id="flowApproved" sourceRef="decisionGateway" targetRef="hrRecord">
<conditionExpression xsi:type="tFormalExpression">${approved == true}</conditionExpression>
</sequenceFlow>
<!-- Rejected flow -->
<sequenceFlow id="flowRejected" sourceRef="decisionGateway" targetRef="rejectEnd">
<conditionExpression xsi:type="tFormalExpression">${approved == false}</conditionExpression>
</sequenceFlow>
<!-- HR record -->
<userTask id="hrRecord" name="HR备案" activiti:assignee="#{hr}"/>
<!-- End Event -->
<endEvent id="endEvent"/>
</process>
</definitions>Java service to deploy and start the process:
@Service
@Transactional
public class LeaveProcessService {
@Autowired
private RuntimeService runtimeService;
@Autowired
private TaskService taskService;
@Autowired
private RepositoryService repositoryService;
// Deploy process definition
public void deployProcess() {
repositoryService.createDeployment()
.addClasspathResource("processes/leave-approval.bpmn20.xml")
.name("请假审批流程")
.deploy();
}
// Start leave process
public void startLeaveProcess(String employee, String leader, int leaveDays) {
Map<String, Object> variables = new HashMap<>();
variables.put("employee", employee);
variables.put("leader", leader);
variables.put("hr", "hr_department");
variables.put("leaveDays", leaveDays);
variables.put("approved", null);
ProcessInstance pi = runtimeService.startProcessInstanceByKey("leaveApproval", variables);
System.out.println("流程启动成功,实例ID:" + pi.getId());
}
// Query pending tasks
public List<Task> getPendingTasks(String assignee) {
return taskService.createTaskQuery()
.taskAssignee(assignee)
.orderByTaskCreateTime().desc()
.list();
}
// Complete task with approval result
public void completeTask(String taskId, boolean approved, String comment) {
Map<String, Object> vars = new HashMap<>();
vars.put("approved", approved);
if (comment != null && !comment.trim().isEmpty()) {
taskService.addComment(taskId, null, comment);
}
taskService.complete(taskId, vars);
System.out.println("任务完成,审批结果:" + (approved ? "通过" : "驳回"));
}
}Flowable: Performance‑Optimized Fork of Activiti
Core Principles
Flowable adds an asynchronous executor and execution‑tree optimization, using finer‑grained lock management and batch processing to improve high‑concurrency performance.
Architecture Improvements
Enhanced asynchronous executor with batch operations
Optimized execution tree reducing DB access
Better cache management and lazy loading
Native Spring Boot starter support
Open‑Source Repository
GitHub: https://github.com/flowable/flowable-engine
Example: Order Processing with Service Tasks
@Service
public class OrderProcessService {
@Autowired
private RuntimeService runtimeService;
@Autowired
private RepositoryService repositoryService;
public void deployOrderProcess() {
repositoryService.createDeployment()
.addClasspathResource("processes/order-process.bpmn20.xml")
.addClasspathResource("processes/order-discount.dmn") // DMN decision table
.deploy();
}
public void startOrderProcess(Order order) {
Map<String, Object> vars = new HashMap<>();
vars.put("order", order);
vars.put("customerService", "cs_team");
vars.put("warehouse", "wh_team");
ProcessInstance pi = runtimeService.startProcessInstanceByKey("orderProcess", order.getOrderNo(), vars);
logger.info("订单流程启动: {}", pi.getId());
}
}
@Component
public class InventoryCheckDelegate implements JavaDelegate {
@Autowired
private InventoryService inventoryService;
@Override
public void execute(DelegateExecution execution) {
Order order = (Order) execution.getVariable("order");
boolean inStock = inventoryService.checkInventory(order.getProductId(), order.getQuantity());
execution.setVariable("inStock", inStock);
if (!inStock) {
execution.setVariable("needBackorder", true);
logger.warn("产品{}库存不足,需要备货", order.getProductId());
}
}
}
@Component
public class PaymentProcessDelegate implements JavaDelegate {
@Autowired
private PaymentService paymentService;
@Override
public void execute(DelegateExecution execution) {
Order order = (Order) execution.getVariable("order");
try {
PaymentResult result = paymentService.processPayment(order);
execution.setVariable("paymentSuccess", result.isSuccess());
execution.setVariable("paymentId", result.getPaymentId());
} catch (Exception e) {
execution.setVariable("paymentSuccess", false);
execution.setVariable("paymentError", e.getMessage());
throw new RuntimeException("支付处理失败", e);
}
}
}Camunda: Preferred for Microservice Architectures
Core Principles
Camunda uses an external‑task model and optimistic‑lock concurrency control, decoupling the workflow engine from business services and enabling language‑agnostic workers via task queues.
Architecture Features
External task mode supports multi‑language clients
Full monitoring tools (Cockpit, Tasklist)
Optimistic lock for high concurrency
Native microservice integration
Open‑Source Repository
GitHub: https://github.com/camunda/camunda-bpm-platform
Example: Loan Approval with External Tasks
@Service
@Transactional
public class LoanProcessService {
@Autowired
private RuntimeService runtimeService;
public void startLoanProcess(LoanApplication application) {
Map<String, Object> vars = new HashMap<>();
vars.put("application", application);
vars.put("creditCheckRequired", application.getAmount() > 100000);
vars.put("autoApprovalLimit", 50000.0);
ProcessInstance pi = runtimeService.startProcessInstanceByKey("loanApprovalProcess", application.getApplicationId(), vars);
logger.info("贷款流程启动: {}", pi.getId());
}
}
@Component
public class CreditCheckWorker {
@Autowired
private ExternalTaskService externalTaskService;
@Autowired
private CreditService creditService;
@PostConstruct
public void startCreditCheckWorker() {
new Thread(() -> {
while (true) {
try {
List<LockedExternalTask> tasks = externalTaskService.fetchAndLock(10, "credit-check-worker")
.topic("credit-check", 60000L)
.execute();
for (LockedExternalTask task : tasks) {
processCreditCheck(task);
}
Thread.sleep(5000);
} catch (Exception e) {
logger.error("征信检查工作者异常", e);
}
}
}).start();
}
private void processCreditCheck(LockedExternalTask task) {
try {
LoanApplication app = (LoanApplication) task.getVariable("application");
CreditReport report = creditService.getCreditReport(app.getApplicantName(), app.getApplicantId());
Map<String, Object> vars = new HashMap<>();
vars.put("creditReport", report);
vars.put("creditScore", report.getScore());
vars.put("creditCheckPassed", report.getScore() > 600);
externalTaskService.complete(task.getId(), "credit-check-worker", vars);
logger.info("征信检查完成: {}", app.getApplicationId());
} catch (Exception e) {
externalTaskService.handleFailure(task.getId(), "credit-check-worker", e.getMessage(), 3, 30000L);
}
}
}jBPM: Best Choice for Rule Integration
Core Principles
jBPM uses a Knowledge Session model that unifies the process engine and Drools rule engine, allowing seamless collaboration between workflow and business rules.
Architecture Features
Deep Drools integration
Knowledge‑session unified execution environment
Supports BPMN 2.0 and case management
Full lifecycle management
Open‑Source Repository
GitHub: https://github.com/kiegroup/jbpm
Example: Insurance Claim with Rule Tasks
@Service
public class ClaimProcessService {
@Autowired
private RuntimeEngine runtimeEngine;
@Autowired
private KieContainer kieContainer;
public void startClaimProcess(InsuranceClaim claim) {
KieSession ks = kieContainer.newKieSession();
try {
ks.insert(claim);
ks.insert(new Date());
ProcessInstance pi = runtimeEngine.getKieSession()
.startProcess("claimApprovalProcess", createProcessVariables(claim));
logger.info("理赔流程启动: {}", pi.getId());
} finally {
ks.dispose();
}
}
private Map<String, Object> createProcessVariables(InsuranceClaim claim) {
Map<String, Object> vars = new HashMap<>();
vars.put("claim", claim);
vars.put("claimAmount", claim.getClaimAmount());
vars.put("claimType", claim.getClaimType());
vars.put("autoApprovalLimit", 5000.0);
vars.put("requiresManagerApproval", claim.getClaimAmount() > 10000);
return vars;
}
}
// DRL rule example
rule "High Amount Fraud Detection"
when
$claim : InsuranceClaim(claimAmount > 50000)
Date()
then
$claim.setFraudulent(true);
modify($claim);
System.out.println("检测到大额理赔,标记为可疑欺诈: " + $claim.getClaimId());
endJDEasyFlow: Lightweight Flow Orchestration Star
Core Principles
JDEasyFlow defines processes using JSON and a state‑machine engine, following a "convention over configuration" approach for simple orchestration without a database.
Architecture Features
JSON‑based declarative definitions
In‑memory execution, no DB dependency
Supports a subset of BPMN elements
Very low learning curve
Open‑Source Repository
GitHub: https://github.com/JDEasyFlow/jd-easyflow
Example: Simple Order State Flow
{
"id": "order_flow",
"name": "订单状态流程",
"nodes": [
{"id": "created", "name": "订单创建", "start": true, "post": {"to": "paid"}},
{"id": "paid", "name": "已支付", "action": {"createExp": "new com.example.order.PaymentAction()"}, "post": {"to": "shipped"}},
{"id": "shipped", "name": "已发货", "action": {"createExp": "new com.example.order.ShipmentAction()"}, "post": {"to": "received"}},
{"id": "received", "name": "已收货", "action": {"createExp": "new com.example.order.ReceiveAction()"}, "post": {"to": "completed"}},
{"id": "completed", "name": "已完成"},
{"id": "cancelled", "name": "已取消"}
]
} @Component
public class PaymentAction implements FlowNodeAction {
@Autowired
private PaymentService paymentService;
@Override
public Object execute(FlowRequest request, FlowContext context) {
String orderId = (String) request.getParam("orderId");
PaymentResult result = paymentService.confirmPayment(orderId);
Map<String, Object> data = new HashMap<>();
data.put("paymentId", result.getPaymentId());
data.put("paymentTime", result.getPaymentTime());
data.put("success", result.isSuccess());
logger.info("订单{}支付处理完成", orderId);
return data;
}
}Comprehensive Comparison of the Five Workflow Engines
Feature Dimension | Activiti | Flowable | Camunda | jBPM | JDEasyFlow
--------------------------|----------|----------|---------|------|------------
Learning Curve | Medium | Medium | Steep | Steep| Simple
Performance | Good | Excellent| Excellent| Good | Outstanding
Feature Completeness | High | High | Very High| High | Medium
Monitoring & Ops | Basic | Good | Excellent| Good | Simple
Community Ecosystem | Active | Active | Active | Active| Limited
Cloud‑Native Support | Basic | Good | Excellent| Good | Good
Rule Integration | Basic | Basic | Good | Excellent| None
Typical Use Cases | Traditional enterprise apps | High‑throughput business | Microservice architectures | Rule‑intensive scenarios | Lightweight service orchestrationDecision flowchart (visual guide for engine selection):
Conclusion
Traditional enterprise applications – choose Activiti for mature ecosystem and rich documentation.
High‑performance, high‑concurrency scenarios – choose Flowable for its performance optimizations.
Microservice architectures – choose Camunda for its external‑task model and robust monitoring.
Rule‑intensive business – choose jBPM for deep Drools integration.
Lightweight service orchestration – choose JDEasyFlow for simplicity and low learning cost.
Remember, there is no universally best workflow engine; the optimal choice depends on your team’s tech stack, business requirements, performance needs, and operational capabilities.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.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.
