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

  1. Use unique worker ids per tenant so a worker never holds cross-tenant jobs
  2. Pass null/empty tenantId if cross-tenant release is intended (no tenant filter applied)
  3. Clean up mismatched tenant ids on the jobs or migrate them to the correct tenant
  4. 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

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


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)