On this page

A Camunda 8 job worker behaves under failure when it is written for a job that can arrive twice. A job that outlives its timeout goes to another worker, a failed job is retried until its retries reach zero and an incident stops the instance, and a BPMN error goes to the process model. Because delivery is at least once, every worker must be idempotent.

What happens when a process reaches a service task

#

When a process instance enters a service task, Zeebe, the engine of Camunda 8, creates a job, and the instance waits until the job is complete. The task names the job type and the retries: three, unless the model sets another number. A worker for that type activates the job and answers in one of three ways:

  • Complete the job, with new variables: the instance continues.
  • Fail the job, with the retries that remain: the engine tries again or raises an incident.
  • Throw a BPMN error, with an error code: the model decides what comes next.

The worker runs in your own application, outside the engine, and no transaction spans the two. Teams coming from a Camunda 7 JavaDelegate, which ran inside the engine, find the rest of the move in Camunda 7 to 8: what changes in your models, code and data.

Which Java library to build a worker with

#

Camunda 8.8 replaced both Java libraries for workers, and Camunda 8.10, released on 13 October 2026, completes the change.

  • Camunda Java Client (io.camunda:camunda-client-java) replaces the Zeebe Java client and uses REST by default, with gRPC configurable. The 8.10 release notes say the Zeebe Client is removed.
  • Camunda Spring Boot Starter (camunda-spring-boot-starter) replaces the Spring Zeebe SDK, which Camunda names for removal in 8.10. A method annotated with @JobWorker becomes a worker, and its job is completed when the method returns, unless the annotation sets autoComplete = false.

In 8.10 the default starter is bundled with Spring Boot 4.1, after Spring Boot 3.5 in 8.8 and 4.0 in 8.9. The camunda-spring-boot-3-starter serves Spring Boot 3.5, whose open-source support ended on 30 June 2026; for 4.1 it ends on 31 July 2027. Each 8.10 starter needs JDK 17 or later.

Setting the job timeout

#

A worker activates a job with a timeout. If it neither completes nor fails the job in that time, Zeebe reassigns the job to another worker, without using up a retry. The first worker may still be running: two workers may work on the same job at once, only one completion is accepted, and the other is rejected with a NOT FOUND error.

  • Set the timeout above the slowest real run. In the starter, that is @JobWorker(timeout = 60000) in milliseconds, or an ISO 8601 duration in the configuration.
  • For work of uneven length, the UpdateJobTimeout command extends or shortens the timeout of a job the worker holds. Camunda advises leaving a margin rather than waiting for the last second.
  • Camunda’s 8.10 documentation adds job leasing, an opt-in token for each activation: a completion from an activation that a newer one has replaced is rejected, whichever arrives first. Camunda advises one choice per job type: all its workers lease, or none do.

Failing a job: retries and the retry back-off

#

A worker that cannot finish a job fails it with the retries that remain, usually one fewer: Camunda’s example calls newFailCommand(job).retries(job.getRetries() - 1). Above zero, the engine retries the job; at zero or below, it raises an incident.

A fail command can also carry a retry back-off, a wait before the next attempt. Without one, the job is retried at once, straight into a system that may still be down.

In the Spring Boot Starter, CamundaError.jobError(...) fails a job with a message, variables, the retries (by default one fewer) and a back-off. Any other exception also fails the job with one retry fewer, and with no back-off unless retryBackoff is set on the annotation or as a default.

When retries reach zero: the incident

#

An incident means the process instance is stuck at one point and needs someone to act. A job that fails with no retries left raises one, and so does a BPMN error that nothing in the model catches.

To resolve a job incident, fix the cause, correct the variables if they caused it, increase the job’s retries and mark the incident resolved, in Operate or through the client API. After that, Camunda checks the job again only when a worker next activates it. With no worker connected for the job type, no new incident appears, and the instance can look healthy in Operate.

A BPMN error or a failed job?

#

A failed job says the work could not be done this time: a service did not answer. A BPMN error says the work met a business case the process models, such as an item out of stock. Camunda advises handling technical problems with retries and incidents rather than modelling the retrying, and sending expected results to a gateway.

A worker throws a BPMN error with an error code: newThrowErrorCommand(job).errorCode(...) in the Java client, CamundaError.bpmnError(code, message) in the starter. An error boundary event or an error event subprocess with that code catches it and interrupts the task; without a match, an incident is raised. A schematic after the starter’s examples:

@JobWorker(type = "reserve-stock", timeout = 60000)
public Map<String, Object> reserveStock(@Variable String orderId, @Variable String itemId) {
  try {
    Reservation r = warehouse.reserve(orderId, itemId); // orderId: the same key on every attempt
    if (r.unavailable()) throw CamundaError.bpmnError("ITEM_UNAVAILABLE", "Not in stock");
    return Map.of("reservationId", r.id());
  } catch (WarehouseUnreachableException e) {
    throw CamundaError.jobError("Warehouse unreachable", null, null, Duration.ofSeconds(30), e);
  }
}

Why every job worker must be idempotent

#

Zeebe is an at-least-once system for job delivery, so Camunda says worker code must be idempotent: running it twice for the same job must leave the same state. A crash before completion, or a timeout, leads to a second run. Camunda’s best-practice guide describes these approaches:

  1. Ask first: the worker asks the provider whether it has already seen the request.
  2. Natural idempotency: the call only flips a state, so a repeat changes nothing.
  3. Business idempotency: a business identifier, such as an order number, lets the provider detect a duplicate.
  4. A unique identifier: generated once and sent with every attempt, so the provider spots the repeat.

Job leasing does not lift the rule: the 8.10 page on job workers still asks for idempotent code. A lease decides which completion counts, not what an earlier run has already done in another system.

Testing a worker before production

#

Test the failure paths: the same job run twice, the system behind the worker stopped until an incident appears, and each BPMN error code caught by the model. For tests of the process itself, Camunda Process Test replaces Zeebe Process Test in 8.10.

The versions in this article were checked on 19 October 2026 against Camunda’s documentation for 8.8 to 8.10. How we build Camunda 8 processes and their Java workers is on the page Camunda.

Questions

What happens when a Camunda 8 job times out?

Zeebe reassigns the job to another worker when it is not completed or failed within its timeout, and the timeout does not use up a retry. The first worker may still be running, so two workers may work on the same job at once; only one completion is accepted.

What is the difference between failing a job and throwing a BPMN error?

Failing a job reports a technical problem, which the engine retries until the retries reach zero and an incident is raised. A BPMN error reports a business case with an error code, which an error boundary event or an error event subprocess in the model catches.

Why must a Camunda 8 job worker be idempotent?

A Camunda 8 job worker must be idempotent because Zeebe delivers jobs at least once: after a crash or a timeout, the same job runs again. Running it twice must leave the same state, for example through a business identifier that lets the receiving system detect the duplicate.

Which Java library should we use for Camunda 8 job workers?

Use the Camunda Java Client, or the Camunda Spring Boot Starter in a Spring Boot application: from 8.8 they replace the Zeebe Java client and the Spring Zeebe SDK, and the 8.10 release notes say the Zeebe Client is removed. In 8.10 the default starter is bundled with Spring Boot 4.1, and camunda-spring-boot-3-starter serves Spring Boot 3.5.

Sources

  1. Job workers, Camunda
  2. Service tasks, Camunda
  3. Incidents, Camunda
  4. Error events, Camunda
  5. Deal with problems and exceptions, Camunda
  6. Conceptual differences, Camunda
  7. Java client, Camunda
  8. Camunda Spring Boot Starter, Camunda
  9. Configuration, Camunda
  10. 8.8 Release announcements, Camunda
  11. 8.9 Release announcements, Camunda
  12. 8.10 Release notes, Camunda
  13. Release announcements and release notes, Camunda