Backend Development 12 min read

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

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

Application Configuration (YAML)

<code>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</code>

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

<code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;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">
  &lt;bpmn2:process id="Process_1" isExecutable="true"&gt;
    &lt;bpmn2:startEvent id="StartEvent_1"&gt;
      &lt;bpmn2:outgoing&gt;Flow_18pxcpx&lt;/bpmn2:outgoing&gt;
    &lt;/bpmn2:startEvent&gt;
    &lt;bpmn2:sequenceFlow id="Flow_18pxcpx" sourceRef="StartEvent_1" targetRef="Activity_0vs8hu4" /&gt;
    &lt;bpmn2:userTask id="Activity_0vs8hu4" camunda:assignee="${uid}"&gt;
      &lt;bpmn2:incoming&gt;Flow_18pxcpx&lt;/bpmn2:incoming&gt;
      &lt;bpmn2:outgoing&gt;Flow_0n014x3&lt;/bpmn2:outgoing&gt;
    &lt;/bpmn2:userTask&gt;
    &lt;bpmn2:sequenceFlow id="Flow_0n014x3" sourceRef="Activity_0vs8hu4" targetRef="Activity_0bcruuz" /&gt;
    &lt;bpmn2:userTask id="Activity_0bcruuz" camunda:assignee="${mid}"&gt;
      &lt;bpmn2:incoming&gt;Flow_0n014x3&lt;/bpmn2:incoming&gt;
      &lt;bpmn2:outgoing&gt;Flow_0dsfy6s&lt;/bpmn2:outgoing&gt;
    &lt;/bpmn2:userTask&gt;
    &lt;bpmn2:endEvent id="Event_1xosttx"&gt;
      &lt;bpmn2:incoming&gt;Flow_0dsfy6s&lt;/bpmn2:incoming&gt;
    &lt;/bpmn2:endEvent&gt;
    &lt;bpmn2:sequenceFlow id="Flow_0dsfy6s" sourceRef="Activity_0bcruuz" targetRef="Event_1xosttx" /&gt;
  &lt;/bpmn2:process&gt;
  &lt;bpmndi:BPMNDiagram id="BPMNDiagram_1"&gt;
    &lt;bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1"&gt;
      ... (graphical elements omitted for brevity) ...
    &lt;/bpmndi:BPMNPlane&gt;
  &lt;/bpmndi:BPMNDiagram&gt;
&lt;/bpmn2:definitions&gt;</code>

Deploying the Process

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

<code>@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());
    }
  }
}</code>
<code>@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);
  }
}</code>

Starting the Process

<code>@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");
  }
}
</code>
<code>@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;
}
</code>

Approving Tasks

<code>@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();
  }
}
</code>
<code>@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);
  }
}
</code>

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.

JavaworkflowBPMNSpring 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

login 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.