Skip to main content
Back to Blog
20 December 202514 min read

Flowable: Open-Source BPM, DMN, CMMN, and Forms

FlowableBPMNDMNCMMNOpen Source

Building process automation with Flowable Community Edition. BPMN engine implementation, decision tables, case management, and Spring Boot integration.


Flowable: Open-Source BPM, DMN, CMMN, and Forms

Flowable is a powerful open-source business process engine that supports BPMN 2.0, DMN 1.1, and CMMN 1.1. Originally forked from Activiti, Flowable has evolved into a mature platform for process automation. Here's how to implement Flowable in enterprise applications.

Flowable Architecture Overview

Core Engines

Flowable Engine Architecture:

┌─────────────────────────────────────────────────────────────┐
│                    Flowable Platform                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐        │
│  │   Process   │  │  Decision   │  │    Case     │        │
│  │   Engine    │  │   Engine    │  │   Engine    │        │
│  │   (BPMN)    │  │   (DMN)     │  │   (CMMN)    │        │
│  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘        │
│         │                │                │                │
│         └────────────────┼────────────────┘                │
│                          │                                 │
│  ┌───────────────────────▼───────────────────────┐        │
│  │              Common Services                   │        │
│  │  - Identity   - Jobs   - History  - Events   │        │
│  └───────────────────────┬───────────────────────┘        │
│                          │                                 │
│  ┌───────────────────────▼───────────────────────┐        │
│  │              Database Layer                    │        │
│  │     (PostgreSQL, MySQL, Oracle, H2)           │        │
│  └───────────────────────────────────────────────┘        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Spring Boot Integration

<!-- pom.xml dependencies --> <dependencies> <!-- Flowable Spring Boot Starter --> <dependency> <groupId>org.flowable</groupId> <artifactId>flowable-spring-boot-starter</artifactId> <version>7.0.0</version> </dependency> <!-- Process Engine --> <dependency> <groupId>org.flowable</groupId> <artifactId>flowable-spring-boot-starter-process</artifactId> <version>7.0.0</version> </dependency> <!-- DMN Engine --> <dependency> <groupId>org.flowable</groupId> <artifactId>flowable-spring-boot-starter-dmn</artifactId> <version>7.0.0</version> </dependency> <!-- CMMN Engine --> <dependency> <groupId>org.flowable</groupId> <artifactId>flowable-spring-boot-starter-cmmn</artifactId> <version>7.0.0</version> </dependency> <!-- REST API --> <dependency> <groupId>org.flowable</groupId> <artifactId>flowable-spring-boot-starter-rest</artifactId> <version>7.0.0</version> </dependency> </dependencies>

Application Configuration

# application.yml spring: datasource: url: jdbc:postgresql://localhost:5432/flowable username: flowable password: flowable driver-class-name: org.postgresql.Driver flowable: # Process Engine Configuration process: definition-cache-limit: 512 enable-safe-xml: true # Async Executor async-executor-activate: true async-executor: core-pool-size: 8 max-pool-size: 32 queue-size: 100 # Database database-schema-update: true # History Level history-level: audit # IDM (Identity Management) idm: enabled: true password-encoder: spring # REST API rest: app: authentication-mode: verify-privilege

BPMN Process Implementation

Process Definition

<?xml version="1.0" encoding="UTF-8"?> <definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:flowable="http://flowable.org/bpmn"> <process id="expense-approval" name="Expense Approval" isExecutable="true"> <startEvent id="start" name="Expense Submitted"> <extensionElements> <flowable:formProperty id="employeeId" name="Employee ID" type="string" required="true"/> <flowable:formProperty id="amount" name="Amount" type="double" required="true"/> <flowable:formProperty id="description" name="Description" type="string"/> </extensionElements> </startEvent> <sequenceFlow id="flow1" sourceRef="start" targetRef="checkAmount"/> <!-- Exclusive Gateway: Check Amount --> <exclusiveGateway id="checkAmount" name="Amount Check"/> <sequenceFlow id="flow-auto" sourceRef="checkAmount" targetRef="autoApprove"> <conditionExpression xsi:type="tFormalExpression"> ${amount &lt;= 100} </conditionExpression> </sequenceFlow> <sequenceFlow id="flow-manager" sourceRef="checkAmount" targetRef="managerApproval"> <conditionExpression xsi:type="tFormalExpression"> ${amount &gt; 100 &amp;&amp; amount &lt;= 1000} </conditionExpression> </sequenceFlow> <sequenceFlow id="flow-director" sourceRef="checkAmount" targetRef="directorApproval"> <conditionExpression xsi:type="tFormalExpression"> ${amount &gt; 1000} </conditionExpression> </sequenceFlow> <!-- Auto Approve Service Task --> <serviceTask id="autoApprove" name="Auto Approve" flowable:delegateExpression="${autoApproveDelegate}"/> <sequenceFlow id="flow2" sourceRef="autoApprove" targetRef="processPayment"/> <!-- Manager Approval User Task --> <userTask id="managerApproval" name="Manager Approval" flowable:candidateGroups="managers"> <extensionElements> <flowable:formProperty id="approved" name="Approved" type="boolean" required="true"/> <flowable:formProperty id="comments" name="Comments" type="string"/> </extensionElements> </userTask> <sequenceFlow id="flow3" sourceRef="managerApproval" targetRef="checkApproval"/> <!-- Director Approval User Task --> <userTask id="directorApproval" name="Director Approval" flowable:candidateGroups="directors"> <extensionElements> <flowable:formProperty id="approved" name="Approved" type="boolean" required="true"/> <flowable:formProperty id="comments" name="Comments" type="string"/> </extensionElements> </userTask> <sequenceFlow id="flow4" sourceRef="directorApproval" targetRef="checkApproval"/> <!-- Check Approval Gateway --> <exclusiveGateway id="checkApproval" name="Approved?"/> <sequenceFlow id="flow-approved" sourceRef="checkApproval" targetRef="processPayment"> <conditionExpression>${approved == true}</conditionExpression> </sequenceFlow> <sequenceFlow id="flow-rejected" sourceRef="checkApproval" targetRef="notifyRejection"> <conditionExpression>${approved == false}</conditionExpression> </sequenceFlow> <!-- Process Payment Service Task --> <serviceTask id="processPayment" name="Process Payment" flowable:delegateExpression="${paymentDelegate}"/> <sequenceFlow id="flow5" sourceRef="processPayment" targetRef="endApproved"/> <!-- Notify Rejection Service Task --> <serviceTask id="notifyRejection" name="Notify Rejection" flowable:expression="${notificationService.sendRejection(execution)}"/> <sequenceFlow id="flow6" sourceRef="notifyRejection" targetRef="endRejected"/> <!-- End Events --> <endEvent id="endApproved" name="Expense Approved"/> <endEvent id="endRejected" name="Expense Rejected"/> </process> </definitions>

Java Delegate Implementation

@Component("paymentDelegate") public class PaymentDelegate implements JavaDelegate { private final PaymentService paymentService; private final AuditService auditService; public PaymentDelegate(PaymentService paymentService, AuditService auditService) { this.paymentService = paymentService; this.auditService = auditService; } @Override public void execute(DelegateExecution execution) { String employeeId = (String) execution.getVariable("employeeId"); Double amount = (Double) execution.getVariable("amount"); String description = (String) execution.getVariable("description"); // Process payment PaymentResult result = paymentService.processExpensePayment( employeeId, amount, description ); // Set result variables execution.setVariable("paymentId", result.getPaymentId()); execution.setVariable("paymentDate", result.getProcessedDate()); // Audit log auditService.log( "EXPENSE_PAYMENT", employeeId, Map.of( "amount", amount, "paymentId", result.getPaymentId() ) ); } }

Process Service Layer

@Service public class ExpenseProcessService { private final RuntimeService runtimeService; private final TaskService taskService; private final HistoryService historyService; public ExpenseProcessService( RuntimeService runtimeService, TaskService taskService, HistoryService historyService) { this.runtimeService = runtimeService; this.taskService = taskService; this.historyService = historyService; } // Start new expense approval process public String submitExpense(ExpenseRequest request) { Map<String, Object> variables = new HashMap<>(); variables.put("employeeId", request.getEmployeeId()); variables.put("amount", request.getAmount()); variables.put("description", request.getDescription()); variables.put("submittedDate", LocalDateTime.now()); ProcessInstance instance = runtimeService.startProcessInstanceByKey( "expense-approval", request.getEmployeeId() + "-" + System.currentTimeMillis(), variables ); return instance.getId(); } // Get pending tasks for user public List<TaskInfo> getPendingTasks(String userId) { List<Task> tasks = taskService.createTaskQuery() .taskCandidateOrAssigned(userId) .orderByTaskCreateTime() .desc() .list(); return tasks.stream() .map(this::mapToTaskInfo) .collect(Collectors.toList()); } // Complete approval task public void completeApproval(String taskId, boolean approved, String comments) { Map<String, Object> variables = new HashMap<>(); variables.put("approved", approved); variables.put("comments", comments); variables.put("completedDate", LocalDateTime.now()); taskService.complete(taskId, variables); } // Get process history public ProcessHistory getProcessHistory(String processInstanceId) { List<HistoricActivityInstance> activities = historyService .createHistoricActivityInstanceQuery() .processInstanceId(processInstanceId) .orderByHistoricActivityInstanceStartTime() .asc() .list(); HistoricProcessInstance process = historyService .createHistoricProcessInstanceQuery() .processInstanceId(processInstanceId) .singleResult(); return new ProcessHistory(process, activities); } private TaskInfo mapToTaskInfo(Task task) { Map<String, Object> variables = taskService.getVariables(task.getId()); return new TaskInfo( task.getId(), task.getName(), task.getCreateTime(), variables ); } }

DMN Decision Tables

Decision Definition

<?xml version="1.0" encoding="UTF-8"?> <definitions xmlns="http://www.omg.org/spec/DMN/20151101/dmn.xsd" namespace="http://flowable.org/dmn"> <decision id="expense-routing" name="Expense Routing Decision"> <decisionTable id="expense-routing-table" hitPolicy="FIRST"> <input id="input-amount" label="Amount"> <inputExpression typeRef="number"> <text>amount</text> </inputExpression> </input> <input id="input-category" label="Category"> <inputExpression typeRef="string"> <text>category</text> </inputExpression> </input> <output id="output-approver" label="Approver Group" name="approverGroup" typeRef="string"/> <output id="output-sla" label="SLA Hours" name="slaHours" typeRef="number"/> <!-- Rule 1: Travel expenses over 500 --> <rule> <inputEntry><text>&gt; 500</text></inputEntry> <inputEntry><text>"travel"</text></inputEntry> <outputEntry><text>"travel-directors"</text></outputEntry> <outputEntry><text>48</text></outputEntry> </rule> <!-- Rule 2: Equipment over 1000 --> <rule> <inputEntry><text>&gt; 1000</text></inputEntry> <inputEntry><text>"equipment"</text></inputEntry> <outputEntry><text>"it-directors"</text></outputEntry> <outputEntry><text>72</text></outputEntry> </rule> <!-- Rule 3: Any expense over 5000 --> <rule> <inputEntry><text>&gt; 5000</text></inputEntry> <inputEntry><text>-</text></inputEntry> <outputEntry><text>"finance-directors"</text></outputEntry> <outputEntry><text>96</text></outputEntry> </rule> <!-- Rule 4: Default - manager approval --> <rule> <inputEntry><text>-</text></inputEntry> <inputEntry><text>-</text></inputEntry> <outputEntry><text>"managers"</text></outputEntry> <outputEntry><text>24</text></outputEntry> </rule> </decisionTable> </decision> </definitions>

Evaluating Decisions

@Service public class DecisionService { private final DmnRuleService dmnRuleService; public DecisionService(DmnRuleService dmnRuleService) { this.dmnRuleService = dmnRuleService; } public RoutingDecision evaluateExpenseRouting(double amount, String category) { Map<String, Object> variables = new HashMap<>(); variables.put("amount", amount); variables.put("category", category); RuleEngineExecutionResult result = dmnRuleService .createExecuteDecisionBuilder() .decisionKey("expense-routing") .variables(variables) .executeWithSingleResult(); return new RoutingDecision( (String) result.getResultVariables().get("approverGroup"), (Integer) result.getResultVariables().get("slaHours") ); } }

CMMN Case Management

Case Definition

<?xml version="1.0" encoding="UTF-8"?> <definitions xmlns="http://www.omg.org/spec/CMMN/20151109/MODEL" targetNamespace="http://flowable.org/cmmn"> <case id="support-case" name="Customer Support Case"> <casePlanModel id="support-plan" name="Support Case Plan"> <!-- Stage: Triage --> <stage id="triage-stage" name="Triage"> <planItem id="planItem-classify" definitionRef="classify-task"/> <planItem id="planItem-prioritize" definitionRef="prioritize-task"/> </stage> <!-- Stage: Investigation --> <stage id="investigation-stage" name="Investigation"> <entryCriterion id="entry-investigation" sentryRef="sentry-triage-complete"/> <planItem id="planItem-research" definitionRef="research-task"/> <planItem id="planItem-reproduce" definitionRef="reproduce-task"> <itemControl> <manualActivationRule/> </itemControl> </planItem> <planItem id="planItem-escalate" definitionRef="escalate-task"> <itemControl> <manualActivationRule/> </itemControl> </planItem> </stage> <!-- Stage: Resolution --> <stage id="resolution-stage" name="Resolution"> <entryCriterion id="entry-resolution" sentryRef="sentry-investigation-complete"/> <planItem id="planItem-implement" definitionRef="implement-task"/> <planItem id="planItem-verify" definitionRef="verify-task"/> <planItem id="planItem-document" definitionRef="document-task"/> </stage> <!-- Human Tasks --> <humanTask id="classify-task" name="Classify Issue"/> <humanTask id="prioritize-task" name="Set Priority"/> <humanTask id="research-task" name="Research Solution"/> <humanTask id="reproduce-task" name="Reproduce Issue"/> <humanTask id="escalate-task" name="Escalate to Engineering"/> <humanTask id="implement-task" name="Implement Fix"/> <humanTask id="verify-task" name="Verify Resolution"/> <humanTask id="document-task" name="Update Documentation"/> <!-- Sentries --> <sentry id="sentry-triage-complete"> <planItemOnPart sourceRef="planItem-classify"> <standardEvent>complete</standardEvent> </planItemOnPart> <planItemOnPart sourceRef="planItem-prioritize"> <standardEvent>complete</standardEvent> </planItemOnPart> </sentry> <sentry id="sentry-investigation-complete"> <planItemOnPart sourceRef="planItem-research"> <standardEvent>complete</standardEvent> </planItemOnPart> </sentry> <!-- Milestone --> <milestone id="case-resolved" name="Case Resolved"> <entryCriterion sentryRef="sentry-resolution-complete"/> </milestone> <sentry id="sentry-resolution-complete"> <planItemOnPart sourceRef="planItem-verify"> <standardEvent>complete</standardEvent> </planItemOnPart> </sentry> </casePlanModel> </case> </definitions>

Case Service Implementation

@Service public class SupportCaseService { private final CmmnRuntimeService cmmnRuntimeService; private final CmmnTaskService cmmnTaskService; private final CmmnHistoryService cmmnHistoryService; // Start new support case public String createSupportCase(SupportCaseRequest request) { Map<String, Object> variables = new HashMap<>(); variables.put("customerId", request.getCustomerId()); variables.put("subject", request.getSubject()); variables.put("description", request.getDescription()); variables.put("createdDate", LocalDateTime.now()); CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder() .caseDefinitionKey("support-case") .businessKey(request.getCaseNumber()) .variables(variables) .start(); return caseInstance.getId(); } // Get available plan items (tasks that can be activated) public List<PlanItemInfo> getAvailablePlanItems(String caseId) { List<PlanItemInstance> planItems = cmmnRuntimeService .createPlanItemInstanceQuery() .caseInstanceId(caseId) .planItemInstanceStateEnabled() .list(); return planItems.stream() .map(this::mapToPlanItemInfo) .collect(Collectors.toList()); } // Manually activate discretionary task public void activateTask(String caseId, String planItemId) { cmmnRuntimeService.startPlanItemInstance(planItemId); } // Complete task public void completeTask(String taskId, Map<String, Object> variables) { cmmnTaskService.complete(taskId, variables); } }

Flowable Forms

Form Definition (JSON)

{ "key": "expense-form", "name": "Expense Request Form", "fields": [ { "id": "employeeId", "name": "Employee ID", "type": "text", "required": true, "placeholder": "Enter employee ID" }, { "id": "amount", "name": "Amount", "type": "decimal", "required": true, "params": { "minValue": 0.01 } }, { "id": "category", "name": "Category", "type": "dropdown", "required": true, "options": [ { "id": "travel", "name": "Travel" }, { "id": "equipment", "name": "Equipment" }, { "id": "supplies", "name": "Office Supplies" }, { "id": "training", "name": "Training" }, { "id": "other", "name": "Other" } ] }, { "id": "description", "name": "Description", "type": "multi-line-text", "required": true }, { "id": "receipt", "name": "Receipt", "type": "upload", "params": { "multiple": false, "accept": ".pdf,.jpg,.png" } } ] }

REST API Endpoints

Process Management

@RestController @RequestMapping("/api/processes") public class ProcessController { private final RuntimeService runtimeService; private final TaskService taskService; @PostMapping("/start/{processKey}") public ProcessInstanceResponse startProcess( @PathVariable String processKey, @RequestBody Map<String, Object> variables) { ProcessInstance instance = runtimeService .startProcessInstanceByKey(processKey, variables); return new ProcessInstanceResponse( instance.getId(), instance.getProcessDefinitionId(), instance.getBusinessKey() ); } @GetMapping("/tasks") public List<TaskResponse> getTasks( @RequestParam(required = false) String assignee, @RequestParam(required = false) String candidateGroup) { TaskQuery query = taskService.createTaskQuery(); if (assignee != null) { query.taskAssignee(assignee); } if (candidateGroup != null) { query.taskCandidateGroup(candidateGroup); } return query.list().stream() .map(TaskResponse::fromTask) .collect(Collectors.toList()); } @PostMapping("/tasks/{taskId}/complete") public void completeTask( @PathVariable String taskId, @RequestBody Map<String, Object> variables) { taskService.complete(taskId, variables); } }

Key Takeaways

  1. Spring Boot integration: Flowable's starters make embedding straightforward—auto-configuration handles most setup

  2. Three engines, one platform: Process (BPMN), Decision (DMN), and Case (CMMN) engines work together seamlessly

  3. Delegate expressions: Use Spring beans as delegates for clean separation of process logic and business logic

  4. History service: Flowable's audit history provides comprehensive process analytics out of the box

  5. REST API included: The REST starter exposes full API—extend with custom endpoints as needed

  6. Open source flexibility: No vendor lock-in, full source access, and active community

Flowable provides enterprise-grade process automation without licensing costs. The combination of BPMN, DMN, and CMMN support makes it suitable for complex workflow requirements. \

Share this article