Step‑by‑Step: Configure, Deploy, and Run a Camunda BPM Workflow with Spring Boot

This guide walks through setting up Camunda BPM 7.18 with Spring Boot 2.6.14, configuring dependencies and application properties, designing a two‑step approval workflow, deploying it via custom REST endpoints, and managing process start and task approval using Camunda’s services and APIs.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Step‑by‑Step: Configure, Deploy, and Run a Camunda BPM Workflow with Spring Boot

Environment

Spring Boot 2.6.14 combined with camunda-spring-boot-starter version 7.18.0.

Dependency Configuration

<camunda.version>7.18.0</camunda.version>
<dependency>
  <groupId>org.camunda.bpm.springboot</groupId>
  <artifactId>camunda-bpm-spring-boot-starter-webapp</artifactId>
  <version>${camunda.version}</version>
</dependency>
<dependency>
  <groupId>org.camunda.bpm.springboot</groupId>
  <artifactId>camunda-bpm-spring-boot-starter-rest</artifactId>
  <version>${camunda.version}</version>
</dependency>

Application Configuration (YAML)

camunda.bpm:
  webapp:
    # Set admin console context path
    application-path: /workflow
  auto-deployment-enabled: true
  admin-user:
    # Admin user for console
    id: admin
    password: admin
    firstName: admin
  filter:
    create: All tasks
  database:
    # Database type
    type: mysql
    # Auto‑update schema
    schema-update: true
logging:
  level:
    # Enable SQL logging during development
    '[org.camunda.bpm.engine.impl.persistence.entity]': debug
---
spring:
  jersey:
    application-path: /api-flow
    type: servlet
    servlet:
      load-on-startup: 0

Accessing the Console

After the above configuration, the Camunda admin console is reachable at:

http://localhost:8100/workflow/

Designing the Process

The example defines a two‑node approval workflow: manager approval → HR approval. Each user task is assigned via an expression that resolves the approver.

BPMN XML

<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" id="sample-diagram" targetNamespace="http://pack.org/bpmn" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd">
  <bpmn2:process id="Process_1" isExecutable="true">
    <bpmn2:startEvent id="StartEvent_1">
      <bpmn2:outgoing>Flow_18pxcpx</bpmn2:outgoing>
    </bpmn2:startEvent>
    <bpmn2:sequenceFlow id="Flow_18pxcpx" sourceRef="StartEvent_1" targetRef="Activity_0vs8hu4" />
    <bpmn2:userTask id="Activity_0vs8hu4" camunda:assignee="${uid}">
      <bpmn2:incoming>Flow_18pxcpx</bpmn2:incoming>
      <bpmn2:outgoing>Flow_0n014x3</bpmn2:outgoing>
    </bpmn2:userTask>
    <bpmn2:sequenceFlow id="Flow_0n014x3" sourceRef="Activity_0vs8hu4" targetRef="Activity_0bcruuz" />
    <bpmn2:userTask id="Activity_0bcruuz" camunda:assignee="${mid}">
      <bpmn2:incoming>Flow_0n014x3</bpmn2:incoming>
      <bpmn2:outgoing>Flow_0dsfy6s</bpmn2:outgoing>
    </bpmn2:userTask>
    <bpmn2:endEvent id="Event_1xosttx">
      <bpmn2:incoming>Flow_0dsfy6s</bpmn2:incoming>
    </bpmn2:endEvent>
    <bpmn2:sequenceFlow id="Flow_0dsfy6s" sourceRef="Activity_0bcruuz" targetRef="Event_1xosttx" />
  </bpmn2:process>
  <bpmndi:BPMNDiagram id="BPMNDiagram_1">
    <bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
      ... (graphical elements omitted for brevity) ...
    </bpmndi:BPMNPlane>
  </bpmndi:BPMNDiagram>
</bpmn2:definitions>

Deploying the Process

A custom REST controller uploads the BPMN file and invokes a service that uses Camunda’s RepositoryService to create a deployment.

@RestController
@RequestMapping("/camunda")
public class BpmnController {

  @Value("${gx.camunda.upload}")
  private String path;

  @Resource
  private ProcessService processService;

  @PostMapping("/bpmn/upload")
  public AjaxResult uploadFile(MultipartFile file, String fileName, String name) throws Exception {
    try {
      InputStream is = file.getInputStream();
      File storageFile = new File(path + File.separator + fileName);
      FileOutputStream fos = new FileOutputStream(storageFile);
      byte[] buf = new byte[10 * 1024];
      int len;
      while ((len = is.read(buf)) > -1) {
        fos.write(buf, 0, len);
      }
      fos.close();
      is.close();
      processService.createDeploy(fileName, name, new FileSystemResource(storageFile));
      return AjaxResult.success();
    } catch (Exception e) {
      return AjaxResult.error(e.getMessage());
    }
  }
}
@Resource
private RepositoryService repositoryService;

public void createDeploy(String resourceName, String name, org.springframework.core.io.Resource resource) {
  try {
    Deployment deployment = repositoryService.createDeployment()
      .addInputStream(resourceName, resource.getInputStream())
      .name(name)
      .deploy();
    logger.info("Deployment id: {}", deployment.getId());
    logger.info("Deployment name: {}", deployment.getName());
  } catch (IOException e) {
    throw new RuntimeException(e);
  }
}

Starting the Process

@RestController
@RequestMapping("/process")
public class ProcessController {

  @Resource
  private ProcessService processService;

  @GetMapping("/start/{processDefinitionId}")
  public AjaxResult startProcess(@PathVariable("processDefinitionId") String processDefinitionId) {
    Map<String, Object> variables = new HashMap<>();
    variables.put("uid", "1");
    variables.put("mid", "1000");
    processService.startProcessInstanceAssignVariables(processDefinitionId, "AKF", variables);
    return AjaxResult.success("Process started successfully");
  }
}
@Resource
private RuntimeService runtimeService;

public ProcessInstance startProcessInstanceAssignVariables(String processDefinitionId, String businessKey, Map<String, Object> variables) {
  ProcessInstance pi = runtimeService.startProcessInstanceById(processDefinitionId, businessKey, variables);
  logger.info("Process definition ID: {}", pi.getProcessDefinitionId());
  logger.info("Process instance ID: {}", pi.getId());
  logger.info("Business key: {}", pi.getBusinessKey());
  return pi;
}

Approving Tasks

@RestController
@RequestMapping("/process")
public class ProcessController {

  @Resource
  private ProcessService processService;

  @GetMapping("/approve/{id}")
  public AjaxResult approve(@PathVariable("id") String instanceId) {
    if (StringUtils.isEmpty(instanceId)) {
      return AjaxResult.error("Unknown approval task");
    }
    Map<String, Object> variables = new HashMap<>();
    variables.put("uid", "1");
    processService.executionTask(variables, instanceId, task -> {}, null);
    return AjaxResult.success();
  }
}
@Resource
private TaskService taskService;
@Resource
private RuntimeService runtimeService;

@Transactional
public void executionTask(Map<String, Object> variables, String instanceId, Consumer<Task> consumer, String type) {
  Task task = taskService.createTaskQuery().processInstanceId(instanceId).singleResult();
  if (task == null) {
    logger.error("Task {} does not exist", instanceId);
    throw new RuntimeException("Task " + instanceId + " does not exist");
  }
  taskService.setVariables(task.getId(), variables);
  taskService.complete(task.getId(), variables);
  long count = runtimeService.createExecutionQuery().processInstanceId(instanceId).count();
  if (count == 0) {
    consumer.accept(task);
  }
}

Result

After deployment, start, and approval, the full lifecycle – design → deployment → start → approval – is demonstrated, and the current pending approval tasks can be viewed in the Camunda task list.

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.

javaSpring BootREST APIprocess automationCamunda
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

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.