flowable/flowable-engine · error · FlowableIllegalArgumentException

Can only terminate CMMN external job. Job with id '${jobId}'

Error message

Can only terminate CMMN external job. Job with id '${jobId}' is from scope '${scopeType}'

What it means

FlowableIllegalArgumentException thrown when termination is requested for an external worker job whose scopeType is not CMMN (typically a BPMN process job). CMMN termination transitions apply only to CMMN case jobs; BPMN jobs must be completed or failed via their own endpoints. The message includes the offending job id and actual scope type.

Source

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

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

        if (ScopeTypes.CMMN.equals(job.getScopeType())) {
            if (cmmnManagementService != null) {
                if (restApiInterceptor != null) {
                    restApiInterceptor.cmmnTerminateExternalWorkerJob(job, request);
                }

                cmmnManagementService.createCmmnExternalWorkerTransitionBuilder(job.getId(), workerId)
                        .variables(extractVariables(request.getVariables()))
                        .terminate();
            } else {
                throw new FlowableException("Cannot complete CMMN job. There is no CMMN engine available");
            }
        } else {
            throw new FlowableIllegalArgumentException(
                    "Can only terminate CMMN external job. Job with id '" + jobId + "' is from scope '" + job.getScopeType() + "'");
        }

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

    @ApiOperation(value = "Fail an External Worker Job", code = 204, tags = { "Acquire and Execute" })
    @ApiResponses({
            @ApiResponse(code = 204, message = "Indicates the job was successfully completed."),
            @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}/fail", produces = "application/json")
    public ResponseEntity<?> failJob(@PathVariable String jobId, @RequestBody ExternalWorkerJobFailureRequest request) {
        String workerId = request.getWorkerId();
        if (StringUtils.isEmpty(workerId)) {
            throw new FlowableIllegalArgumentException("workerId is required");

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Inspect the acquired job's scopeType and branch: use failJob/bpmnError for BPMN jobs, cmmnTerminate for CMMN jobs.
  2. Filter acquire requests or acquired job lists to CMMN jobs if the worker only handles cases.
  3. Log the jobId and scopeType on failure to catch id-mixing bugs in worker loops.

Example fix

// before
transitionBuilder.terminate();

// after
if (ScopeTypes.CMMN.equals(job.getScopeType())) {
    transitionBuilder.terminate();
} else {
    managementService.createExternalWorkerCompletionBuilder(job.getId(), workerId).fail(errorMessage);
}
Defensive patterns

Strategy: validation

Validate before calling

if (job.scopeType !== 'cmmn') {
  throw new Error('job ' + job.id + ' is scope ' + job.scopeType + '; cmmnTerminate is CMMN-only');
}

Type guard

function isCmmnJob(job) { return job?.scopeType === 'cmmn'; }

Try / catch

try {
  await api.cmmnTerminate(jobId, req);
} catch (e) {
  if (e.status === 400 && /scope/.test(e.message)) {
    return routeByScope(jobId, req); // use fail/bpmnError for BPMN jobs
  }
  throw e;
}

Prevention

When it happens

Trigger: POST /acquire/jobs/{jobId}/cmmnTerminate with a jobId resolving to a job whose getScopeType() is not 'cmmn' (e.g. 'bpmn').

Common situations: Generic worker loops that call terminate for every failed job regardless of scope; mixed-engine deployments where scope type was not inspected; off-by-one/id-swap bugs in batch processing of acquired jobs.

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/b45d51888400db59. Report an issue: GitHub.