flowable/flowable-engine · warning · FlowableObjectNotFoundException

Execution ' ' does not have a variable ' ' in scope

Error message

Execution '${executionId}' does not have a variable '${variableName}' in scope ${scope}

What it means

FlowableObjectNotFoundException thrown by deleteVariable when the target execution does not have the requested variable in the requested scope (local by default, or global if scope=global). Flowable treats deleting a non-existent variable as a not-found condition and includes the execution id, variable name, and scope in the message. Nothing is deleted when this is thrown.

Solutions

  1. Verify the variable exists with GET /runtime/process-instances/{id}/variables and check its scope
  2. Pass ?scope=global if the variable was set on the process instance rather than the local execution
  3. Check you are using the correct execution id (child executions may hold local variables)
  4. Handle 404 gracefully and treat it as idempotent deletion in retry/idempotency logic
  5. Catch FlowableObjectNotFoundException (HTTP 404) around the DELETE call

Example fix

// before
delete(`/runtime/process-instances/${id}/variables/total`)
// after
const vars = await get(`/runtime/process-instances/${id}/variables`);
if (vars.some(v => v.name === 'total')) {
  await delete(`/runtime/process-instances/${id}/variables/total`);
}
Defensive patterns

Strategy: try-catch

Validate before calling

const vars = await fetch(`/runtime/process-instances/${executionId}/variables`).then(r => r.json());
const exists = vars.some(v => v.name === variableName && (scope ? v.scope === scope : true));
if (!exists) return; // already absent; skip delete

Type guard

function variableInScope(v, name, scope) {
  return v.name === name && (scope ? v.scope?.toLowerCase() === scope.toLowerCase() : v.scope === 'local');
}

Try / catch

try { await deleteVariable(executionId, variableName, scope); } catch (e) { if (e.status === 404) { /* treat as already deleted */ return; } throw e; }

Prevention

When it happens

Trigger: DELETE /runtime/process-instances/{executionId}/variables/{variableName} (optionally ?scope=global|local) where the variable was never set, was already deleted, or exists only in the other scope (e.g. set globally but deleting with default local scope).

Common situations: Race conditions where a concurrent request or process end already removed the variable, deleting a child-execution local variable via the parent execution id, expecting variable inheritance from the process definition (variables are runtime-scoped, not inherited).

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


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

Appendix: source

Thrown at modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/process/ExecutionVariableResource.java:198

    @ApiOperation(value = "Delete a variable for an execution", tags = { "Executions" }, nickname = "deletedExecutionVariable", code = 204)
    @ApiResponses(value = {
            @ApiResponse(code = 204, message = "Indicates both the execution and variable were found and variable has been deleted."),
            @ApiResponse(code = 404, message = "Indicates the requested execution was not found or the execution does not have a variable with the given name in the requested scope. Status description contains additional information about the error.")
    })
    @DeleteMapping(value = "/runtime/executions/{executionId}/variables/{variableName}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteVariable(@ApiParam(name = "executionId") @PathVariable("executionId") String executionId, @ApiParam(name = "variableName") @PathVariable("variableName") String variableName,
            @RequestParam(value = "scope", required = false) String scope) {

        Execution execution = getExecutionFromRequestWithoutAccessCheck(executionId);
        // Determine scope
        RestVariableScope variableScope = RestVariableScope.LOCAL;
        if (scope != null) {
            variableScope = RestVariable.getScopeFromString(scope);
        }

        if (!hasVariableOnScope(execution, variableName, variableScope)) {
            throw new FlowableObjectNotFoundException("Execution '" + execution.getId() + "' does not have a variable '" + variableName + "' in scope " + variableScope.name().toLowerCase(),
                    VariableInstance.class);
        }

        if (restApiInterceptor != null) {
            restApiInterceptor.deleteExecutionVariables(execution, Collections.singleton(variableName), variableScope);
        }

        if (variableScope == RestVariableScope.LOCAL) {
            runtimeService.removeVariableLocal(execution.getId(), variableName);
        } else {
            // Safe to use parentId, as the hasVariableOnScope would have
            // stopped a global-var update on a root-execution
            runtimeService.removeVariable(execution.getParentId(), variableName);
        }
    }
}

View on GitHub (pinned to d6d39ce1c6)