Filtering acquired jobs

Hi,

In Camunda 7 when using external tasks we could use this Spring annotation to allow us to filter the jobs to be retrieved and processed.

@ExternalTaskSubscription(
    topicName = "atopic",
    variableNames = {"VAR1", "VAR2"},
    processVariables = {
        @ProcessVariable(name = "FILTER1", value = "BOB"),
        @ProcessVariable(name = "FILTER2", value = "DAVE")
    }
)

Is there a way to achieve this in camunda 8? As i do not see any options on the api for activate jobs.

Thanks,
Matt

Hi Matt,
In Camunda 8 it’s called workers you can implement like below
Here are the dependencies you can use

<dependency>
			<groupId>io.camunda</groupId>
			<artifactId>zeebe-client-java</artifactId>
			<version>8.6.10</version>
		</dependency>
		<dependency>
			<groupId>io.camunda</groupId>
			<artifactId>spring-boot-starter-camunda-sdk</artifactId>
			<version>8.6.10</version>
			<scope>compile</scope>
		</dependency>
@JobWorker(type = "atopic")
    public Map<String, Object> aTopicWorker(final ActivatedJob activatedJob, JobClient jobClient) {
        Map<String, Object> variables = new HashMap<>();
        try {
           String var1=activatedJob.getVariable("VAR1");
           variables.put("VAR1", "updatedValue");
        } catch (RuntimeException e) {
            variables.put("error", new Error("INTERNAL_ERROR", 500, "error message dummy"));
            log.error("Exception occurred while:", e);

            throw new ZeebeBpmnError("INTERNAL_ERROR", "message", variables);
        }
        return variables;
    }

Thanks,
Ranga

Thanks, but unfortunately it doesn’t answer if its possible to filter the jobs being acquired like we could in camunda 7

Job filtering based on variables value does not support by default in Camunda 8, we need to try alternatives like acquire all jobs and complete only which meets filter criteria.

Hi @matt ,
Can you help me understand?
Why would you want your worker to only take some of the work that it is capable of doing? To me that would be like saying “Forklift driver, move all the boxes from Point A to Point B, but only if the box is Blue” If the forklift driver isn’t qualified to move Blue boxes, then it’s a different type of work and should have a different worker, shouldn’t it?

For RuntimeException you shouldn’t be handling through ZeebeBpmnError. When technical error occurs just log the exception and rethrow it so the incident will get created. You can retry for few exceptions like timeout or network related exceptions.

ZeebeBpmnError mainly used to handle business errors and when thrown it should be handled in workflow, otherwise it makes no sense by just throwing ZeebeBpmnError.

To elaborate:

:white_check_mark: When to Use ZeebeBpmnError

ZeebeBpmnError is intended only for business exceptions that:

  • Are expected as part of the process logic (e.g., “Invalid Credit Score”, “Stock Not Available”).
  • Are handled explicitly in the BPMN model using an Error Boundary Event with a matching errorCode.

Example:

if (!isCustomerEligible(customer)) {
    throw new ZeebeBpmnError("CUSTOMER_INELIGIBLE", "Customer does not meet eligibility criteria");
}

This throws a business error, which must be caught in the BPMN via an error boundary event:

Service Task --> (boundary error event with errorCode="CUSTOMER_INELIGIBLE") --> Alternative Path

If there’s no boundary event for that error code, then the error will cause an incident anyway — which defeats the purpose.


:x: When Not to Use ZeebeBpmnError

Don’t use it for technical exceptions such as:

  • NullPointerException
  • Network timeouts
  • Database errors
  • REST call failures

Instead:

  • Log the error.
  • Let the worker fail naturally by rethrowing the RuntimeException.

This way:

  • Zeebe will trigger incident handling.
  • You can inspect the root cause in Operate.
  • Retry behavior can be configured using retry count/backoff/etc.

:white_check_mark: Suggested Handling Pattern

try {
    // Business logic
} catch (BusinessValidationException e) {
    throw new ZeebeBpmnError("BUSINESS_ERROR_CODE", e.getMessage());
} catch (TimeoutException | IOException e) {
    // Optional: implement retry mechanism or let Zeebe retry
    logger.warn("Temporary technical issue, will retry", e);
    throw e;
} catch (Exception e) {
    logger.error("Unhandled technical exception", e);
    throw e; // triggers incident
}

Summary

Exception Type Use ZeebeBpmnError? Handled in BPMN? Triggers Incident?
Business logic error :white_check_mark: Yes :white_check_mark: Yes (must be) :x: No (if handled)
Technical/runtime error :x: No :x: No :white_check_mark: Yes

Hi @GotnOGuts

So we have essentially an integration layer that acts as a common interface to several different implementations of a service. We have a set of core workers that can handle all the common bits, but due to dependency conflicts of third party libraries we need to use, each implementation workers are defined in their own micro services.
As there is only the need for one workflow, in camunda 7 the easiest way to achieve the correct worker picking up the job was with the variable filtering.
Unfortunately this is no longer an option in camunda 8 so we are going to use FEEL to create the worker type by concatenating the variables to the type definition and do the same in the code of the micro services.

1 Like

@matt
I recall something about the workers being able to be tenant aware, so you could have the workflow created for each tenant, and then have the appropriate version picked up.

But realistically, if it’s a different set of work, it should be a different worker.

Thanks for explaining!

1 Like

I dont think you can achieve it with Camunda up to the 8.7.
If you take a look on Zeebe job activating API: Zeebe API RPCs | Camunda 8 Docs then there’s no way to filter jobs that will be acquired besides the job type. You could use multi tenancy feature to achieve your goal, but it will require some changes in the BPMN model, so i guess you could as well just create dedicated job types instead of filtering it by variables.