Developing BPMN Processes with Camunda
Camunda is a platform for orchestrating business processes based on the BPMN 2.0 standard. Suitable for long-running workflows involving people (Human Tasks), external services, and complex conditional logic. Unlike message queues, Camunda provides visualization of each process instance's state.
When Camunda is Needed
- Processes last hours/days/weeks (credit application, employee onboarding)
- People are involved in the process — requires UI for tasks
- Audit trail needed: who made what decision and when
- Conditional routing based on business rules
- Compensations on failures (like Saga Pattern)
BPMN Diagram: Credit Application Processing
[Start Event: Application received]
│
[Service Task: Check credit history]
│
[Gateway (XOR): Credit score > 600?]
│ │
Yes No
│ │
[User Task: [End Event:
Manager Rejection]
approval]
│
[Gateway (XOR): Manager decision?]
│ │
Approved Rejected
│ │
[Service Task: [End Event: Rejection]
Issue credit]
│
[End Event: Success]
Camunda 8 (SaaS) vs Camunda 7 (self-hosted)
| Camunda 8 | Camunda 7 | |
|---|---|---|
| Engine | Zeebe (cloud-native) | Java Process Engine |
| Deployment | SaaS or self-hosted | Self-hosted (Spring Boot) |
| Worker | External Task Workers | Java/External |
| Scaling | Horizontal | Vertical |
| License | Freemium | Apache 2.0 (community) |
Spring Boot + Camunda 7
<!-- pom.xml -->
<dependency>
<groupId>org.camunda.bpm.springboot</groupId>
<artifactId>camunda-bpm-spring-boot-starter</artifactId>
<version>7.20.0</version>
</dependency>
// Deploy BPMN from classpath
@Configuration
public class ProcessEngineConfig {
@Bean
public ProcessEnginePlugin deployProcesses() {
return new ProcessEnginePlugin() {
@Override
public void postInit(ProcessEngineConfigurationImpl config) {
config.setDeploymentResources(new String[] {
"classpath*:processes/*.bpmn"
});
}
};
}
}
Service Task Implementation:
@Component("creditCheckDelegate")
public class CreditCheckDelegate implements JavaDelegate {
@Autowired
private CreditBureauService creditBureauService;
@Override
public void execute(DelegateExecution execution) throws Exception {
String applicantId = (String) execution.getVariable("applicantId");
CreditReport report = creditBureauService.getReport(applicantId);
// Write results to process variables
execution.setVariable("creditScore", report.getScore());
execution.setVariable("creditHistory", report.toJson());
execution.setVariable("scoreApproved", report.getScore() >= 600);
}
}
User Task — task for manager:
@RestController
@RequestMapping("/tasks")
public class TaskController {
@Autowired
private TaskService taskService;
@GetMapping("/manager")
public List<TaskDto> getManagerTasks() {
return taskService.createTaskQuery()
.taskCandidateGroup("credit-managers")
.active()
.list()
.stream()
.map(task -> new TaskDto(
task.getId(),
task.getName(),
runtimeService.getVariables(task.getProcessInstanceId())
))
.toList();
}
@PostMapping("/{taskId}/complete")
public void completeTask(@PathVariable String taskId,
@RequestBody TaskDecisionDto decision) {
taskService.complete(taskId, Map.of(
"managerDecision", decision.getDecision(),
"managerComment", decision.getComment(),
"decidedBy", getCurrentUser().getEmail()
));
}
}
Camunda 8 with Zeebe Java Client
@Component
public class CreditCheckWorker {
@JobWorker(type = "credit-check")
public void handleCreditCheck(final JobClient client, final ActivatedJob job) {
var variables = job.getVariablesAsMap();
String applicantId = (String) variables.get("applicantId");
try {
CreditReport report = creditBureauService.getReport(applicantId);
client.newCompleteCommand(job.getKey())
.variables(Map.of(
"creditScore", report.getScore(),
"scoreApproved", report.getScore() >= 600
))
.send()
.join();
} catch (Exception e) {
client.newFailCommand(job.getKey())
.retries(job.getRetries() - 1)
.errorMessage(e.getMessage())
.send()
.join();
}
}
}
DMN — Business Rules Tables
Camunda supports DMN for complex conditions without code:
| creditScore | loanAmount | employmentYears || decision |
|-------------|------------|-----------------||----------- |
| >= 750 | <= 5000000 | >= 1 || approved |
| >= 700 | <= 2000000 | >= 2 || approved |
| >= 650 | <= 1000000 | >= 3 || manual |
| < 650 | - | - || rejected |
DMN table is invoked in Service Task:
DmnDecisionResult result = decisionService.evaluateDecisionByKey("credit-decision")
.variables(execution.getVariables())
.evaluate();
execution.setVariable("decision", result.getSingleEntry());
Monitoring via Cockpit
Camunda Cockpit — UI for process monitoring: active instances, stuck tasks, incidents, variable audit.
Implementation Timeframe
- One BPMN process with 3–5 Service Tasks and 1–2 User Tasks — 2–3 weeks
- DMN tables for business rules — 3–5 days
- Full system with multiple processes and Cockpit — 1–3 months







