flowable/flowable-engine · error · FlowableIllegalArgumentException
provided worker id has external worker jobs from different…
Error message
provided worker id has external worker jobs from different tenant.
What it means
UnacquireAllExternalWorkerJobsForWorkerCmd.execute throws this FlowableIllegalArgumentException when an explicit tenantId is provided but at least one external worker job currently held by that worker belongs to a different tenant. Flowable refuses the bulk unacquire because it would cross tenant boundaries, enforcing tenant isolation for multi-tenant deployments.
Solutions
- Use unique worker ids per tenant so a worker never holds cross-tenant jobs
- Pass null/empty tenantId if cross-tenant release is intended (no tenant filter applied)
- Clean up mismatched tenant ids on the jobs or migrate them to the correct tenant
- Catch FlowableIllegalArgumentException and fall back to per-job unacquire for matching tenants only
Example fix
// before jobService.unacquireAllExternalWorkerJobsForWorker(workerId, "tenantA"); // fails: worker holds jobs of other tenants // after jobService.unacquireAllExternalWorkerJobsForWorker(workerId, null); // release all jobs of this worker, any tenant
Defensive patterns
Strategy: validation
Validate before calling
// only pass a tenantId if this worker exclusively serves that tenant
boolean tenantMatches = jobs.stream()
.allMatch(j -> Objects.equals(expectedTenantId, j.getTenantId()));
if (!tenantMatches) { /* handle cross-tenant case */ } Try / catch
try {
jobService.unacquireAllExternalWorkerJobsForWorker(workerId, tenantId);
} catch (FlowableIllegalArgumentException e) {
jobService.unacquireAllExternalWorkerJobsForWorker(workerId, null);
} Prevention
- Use unique worker ids per tenant in multi-tenant deployments
- Audit jobs with stale/empty tenant ids after tenant migrations
- Pass null tenantId intentionally when cross-tenant release is desired
When it happens
Trigger: Calling unacquireAllExternalWorkerJobsForWorker(workerId, tenantId) where the worker (shared across tenants, e.g. same worker id registered globally) holds jobs from tenant A and tenant B while the caller passes tenantId=A; jobs created before a tenant migration still carrying an old/empty tenant id.
Common situations: Multi-tenant setups where the same worker id is reused across tenants; tenant id defaulting to empty string on job creation while the caller passes a real tenant id; tenant renaming/migration leaving stale tenant ids on jobs.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- at least one of userId or groups must be provided
- Cannot combine onlyBpmn() with onlyCmmn() in the same query
- Cannot combine onlyCmmn() with onlyBpmn() in the same query
- Cannot combine onlyExternalWorkers() with onlyMessages() in…
- Cannot combine onlyMessages() with onlyExternalWorkers() in…
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/b21b3cf58360f81c.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/cmd/UnacquireAllExternalWorkerJobsForWorkerCmd.java:51
this.tenantId = tenantId;
this.jobServiceConfiguration = jobServiceConfiguration;
}
@Override
public Void execute(CommandContext commandContext) {
if (StringUtils.isEmpty(workerId)) {
throw new FlowableIllegalArgumentException("worker id must not be empty");
}
ExternalWorkerJobEntityManager externalWorkerJobEntityManager = jobServiceConfiguration.getExternalWorkerJobEntityManager();
List<ExternalWorkerJobEntity> jobEntities = externalWorkerJobEntityManager.findJobsByWorkerId(workerId);
if (!jobEntities.isEmpty()) {
if (StringUtils.isNotEmpty(tenantId)) {
for (ExternalWorkerJobEntity externalWorkerJob : jobEntities) {
if (!tenantId.equals(externalWorkerJob.getTenantId())) {
throw new FlowableIllegalArgumentException("provided worker id has external worker jobs from different tenant.");
}
}
}
for (ExternalWorkerJobEntity externalWorkerJob : jobEntities) {
if (externalWorkerJob.isExclusive()) {
new UnlockExclusiveJobCmd(externalWorkerJob, jobServiceConfiguration).execute(commandContext);
}
}
externalWorkerJobEntityManager.bulkUpdateJobLockWithoutRevisionCheck(jobEntities, null, null);
}
return null;
}
}
View on GitHub (pinned to d6d39ce1c6)