flowable/flowable-engine · error · FlowableIllegalArgumentException

Invalid action: ''.

Error message

Invalid action: ''.

What it means

REST validation in executeTaskAction: the 'action' field of the request is not one of the supported task actions (complete, claim, unclaim, delegate, resolve, delete); unknown action names are rejected.

Solutions

  1. Use one of the exact action values: complete, claim, delegate, resolve
  2. Check casing — values are case-sensitive lowercase constants
  3. Log/inspect the serialized body to confirm what 'action' actually contains

Example fix

// before
{"action": "completeTask"}
// after
{"action": "complete"}
Defensive patterns

Strategy: validation

Validate before calling

const ACTIONS=['complete','claim','delegate','resolve']; if (!ACTIONS.includes(action)) throw new Error('Unsupported action: '+action);

Type guard

function isValidAction(a) { return ['complete','claim','delegate','resolve'].includes(a); }

Try / catch

catch (e) { if (e.status === 400 && /Invalid action/.test(e.body && e.body.message)) { /* map to a supported action name and retry */ } else throw e; }

Prevention

When it happens

Trigger: POST /runtime/tasks/{taskId} with body {"action":"foo"}, an empty string action, or wrong casing (e.g. "Complete").

Common situations: Typos in the action string; using action names from other engines (e.g. 'assign' instead of 'claim'); client sending null/empty action field.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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

Appendix: source

Thrown at modules/flowable-rest/src/main/java/org/flowable/rest/service/api/runtime/task/TaskResource.java:137

        }

        if (TaskActionRequest.ACTION_COMPLETE.equals(actionRequest.getAction())) {
            completeTask(task, actionRequest);

        } else if (TaskActionRequest.ACTION_CLAIM.equals(actionRequest.getAction())) {
            claimTask(task, actionRequest);

        } else if (TaskActionRequest.ACTION_UNCLAIM.equals(actionRequest.getAction())) {
            unclaimTask(task);

        } else if (TaskActionRequest.ACTION_DELEGATE.equals(actionRequest.getAction())) {
            delegateTask(task, actionRequest);

        } else if (TaskActionRequest.ACTION_RESOLVE.equals(actionRequest.getAction())) {
            resolveTask(task, actionRequest);

        } else {
            throw new FlowableIllegalArgumentException("Invalid action: '" + actionRequest.getAction() + "'.");
        }
    }

    @ApiOperation(value = "Delete a task", tags = {"Tasks"}, code = 204)
    @ApiImplicitParams({
            @ApiImplicitParam(name = "cascadeHistory", dataType = "string", value = "Whether or not to delete the HistoricTask instance when deleting the task (if applicable). If not provided, this value defaults to false.", paramType = "query"),
            @ApiImplicitParam(name = "deleteReason", dataType = "string", value = "Reason why the task is deleted. This value is ignored when cascadeHistory is true.", paramType = "query")
    })
    @ApiResponses(value = {
            @ApiResponse(code = 204, message = "Indicates the task was found and has been deleted. Response-body is intentionally empty."),
            @ApiResponse(code = 403, message = "Indicates the requested task cannot be deleted because it’s part of a workflow."),
            @ApiResponse(code = 404, message = "Indicates the requested task was not found.")
    })
    @DeleteMapping(value = "/runtime/tasks/{taskId}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteTask(@ApiParam(name = "taskId") @PathVariable String taskId, @ApiParam(hidden = true) @RequestParam(value = "cascadeHistory", required = false) Boolean cascadeHistory,
                           @ApiParam(hidden = true) @RequestParam(value = "deleteReason", required = false) String deleteReason) {

View on GitHub (pinned to d6d39ce1c6)