flowable/flowable-engine · error · FlowableIllegalArgumentException

Can only complete BPMN external job with a BPMN error. Job w

Error message

Can only complete BPMN external job with a BPMN error. Job with id '${jobId}' is from scope '${scopeType}'

What it means

FlowableIllegalArgumentException thrown when a BPMN error is reported for an external worker job whose scopeType is not BPMN (e.g. a CMMN case job). BPMN errors can only be applied to jobs belonging to a BPMN process instance; CMMN jobs must be terminated via the cmmnTerminate endpoint.

Source

Thrown at modules/flowable-external-job-rest/src/main/java/org/flowable/external/job/rest/service/api/acquire/ExternalWorkerAcquireJobResource.java:178

        if (!workerId.equals(job.getLockOwner())) {
            throw new FlowableForbiddenException(workerId + " does not hold a lock on the requested job");
        }

        if (job.getProcessInstanceId() != null) {
            if (managementService != null) {
                if (restApiInterceptor != null) {
                    restApiInterceptor.bpmnErrorExternalWorkerJob(job, request);
                }

                managementService.createExternalWorkerCompletionBuilder(job.getId(), workerId)
                        .variables(extractVariables(request.getVariables()))
                        .bpmnError(request.getErrorCode());
            } else {
                throw new FlowableException("Cannot complete BPMN job. There is no BPMN engine available");
            }
        } else {
            throw new FlowableIllegalArgumentException(
                    "Can only complete BPMN external job with a BPMN error. Job with id '" + jobId + "' is from scope '" + job.getScopeType() + "'");
        }

        return ResponseEntity.noContent().build();
    }

    @ApiOperation(value = "Complete an External Worker Job with a cmmn terminate transition", code = 204, tags = { "Acquire and Execute" })
    @ApiResponses({
            @ApiResponse(code = 204, message = "Indicates the job was successfully transitioned."),
            @ApiResponse(code = 400, message = "Indicates the request was invalid."),
            @ApiResponse(code = 403, message = "Indicates the user does not have the rights complete the job."),
            @ApiResponse(code = 404, message = "Indicates the job does not exist."),
    })
    @PostMapping(value = "/acquire/jobs/{jobId}/cmmnTerminate", produces = "application/json")
    public ResponseEntity<?> terminateCmmnJob(@PathVariable String jobId, @RequestBody ExternalWorkerJobTerminateRequest request) {
        String workerId = request.getWorkerId();
        if (StringUtils.isEmpty(workerId)) {
            throw new FlowableIllegalArgumentException("workerId is required");

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Check job.getScopeType() (available on the acquired ExternalWorkerJob) and call cmmnTerminate for CMMN jobs, bpmnError only for BPMN jobs.
  2. If both engines are used, restrict the acquire request or filter acquired jobs by scope type in the worker.
  3. Verify you are not mixing up jobIds between concurrently processed BPMN and CMMN jobs.

Example fix

// before
completionBuilder.bpmnError(errorCode);

// after
if (ScopeTypes.BPMN.equals(job.getScopeType())) {
    completionBuilder.bpmnError(errorCode);
} else {
    cmmnManagementService.createCmmnExternalWorkerTransitionBuilder(job.getId(), workerId).terminate();
}
Defensive patterns

Strategy: validation

Validate before calling

if (job.scopeType !== 'bpmn') {
  throw new Error('job ' + job.id + ' is scope ' + job.scopeType + '; use cmmnTerminate instead');
}

Type guard

function isBpmnJob(job) { return job?.scopeType === 'bpmn'; }

Try / catch

try {
  await api.bpmnError(jobId, req);
} catch (e) {
  if (e.status === 400 && /scope/.test(e.message)) {
    // wrong endpoint for this job's scope; fetch job and route accordingly
    return routeByScope(jobId, req);
  }
  throw e;
}

Prevention

When it happens

Trigger: POST /acquire/jobs/{jobId}/bpmnError with a jobId that resolves to a job whose getScopeType() returns 'cmmn' (or any non-BPMN scope).

Common situations: Client code that acquires all external jobs indiscriminately and applies bpmnError to every failure; a mixed BPMN+CMMN deployment where the worker doesn't branch on scope type; job id mix-ups when multiple jobs are in flight.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/9f1899b7e0308cc6. Report an issue: GitHub.