flowable/flowable-engine · error · FlowableIllegalArgumentException

Invalid action, only 'move' or 'moveToHistoryJob' is…

Error message

Invalid action, only 'move' or 'moveToHistoryJob' is supported.

What it means

Guard in JobResource.executeDeadLetterJobAction: the action field in the request body for moving a dead letter job is something other than the two supported values 'move' and 'moveToHistoryJob', so the endpoint refuses the request.

Solutions

  1. Use action 'move' to retry the job as executable, or 'moveToHistoryJob' to archive it
  2. Match the action strings exactly, camelCase with no separators
  3. Verify the request body is sent as JSON

Example fix

// before
{"action":"reschedule"}
// after
{"action":"moveToHistoryJob"}
Defensive patterns

Strategy: validation

Validate before calling

const allowed = ['move','moveToHistoryJob'];
if (!body || !allowed.includes(body.action)) throw new Error("deadletter action must be 'move' or 'moveToHistoryJob'");

Type guard

function isValidDeadLetterAction(a) { return a === 'move' || a === 'moveToHistoryJob'; }

Try / catch

try { await post(`/cmmn-management/deadletter-jobs/${id}`, body); }
catch (e) { if (e.status === 400 && /Invalid action/.test(e.message)) { /* fix action to move or moveToHistoryJob */ } else throw e; }

Prevention

When it happens

Trigger: POST /cmmn-management/deadletter-jobs/{jobId} with null body or an unsupported action such as 'execute' or 'reschedule'.

Common situations: Scripts reused from the timer-jobs endpoint (move/reschedule) applied to deadletter jobs, or typos like 'move-to-history-job'.

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

Appendix: source

Thrown at modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/management/JobResource.java:301

        try {
            managementService.executeHistoryJob(historyJob.getId());
        } catch (FlowableObjectNotFoundException e) {
            // Re-throw to have consistent error-messaging across REST-api
            throw new FlowableObjectNotFoundException("Could not find a job with id '" + jobId + "'.", Job.class);
        }
    }
    
    @ApiOperation(value = "Move a single deadletter job. Accepts 'move' and 'moveToHistoryJob' as action.", tags = { "Jobs" }, code = 204)
    @ApiResponses(value = {
            @ApiResponse(code = 204, message = "Indicates the dead letter job was moved. Response-body is intentionally empty."),
            @ApiResponse(code = 404, message = "Indicates the requested job was not found."),
            @ApiResponse(code = 500, message = "Indicates the an exception occurred while executing the job. The status-description contains additional detail about the error. The full error-stacktrace can be fetched later on if needed.")
    })
    @PostMapping("/cmmn-management/deadletter-jobs/{jobId}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void executeDeadLetterJobAction(@ApiParam(name = "jobId") @PathVariable String jobId, @RequestBody RestActionRequest actionRequest) {
        if (actionRequest == null || !(MOVE_ACTION.equals(actionRequest.getAction()) || MOVE_TO_HISTORY_JOB_ACTION.equals(actionRequest.getAction()))) {
            throw new FlowableIllegalArgumentException("Invalid action, only 'move' or 'moveToHistoryJob' is supported.");
        }
        
        Job deadLetterJob = getDeadLetterJobById(jobId);

        if (MOVE_ACTION.equals(actionRequest.getAction())) {

            if (restApiInterceptor != null) {
                restApiInterceptor.moveDeadLetterJob(deadLetterJob, MOVE_ACTION);
            }
            /*
             * Note that the jobType is checked to know which kind of move that needs to be done.
             * The MOVE_TO_HISTORY_JOB_ACTION allows to specifically force the move to a history job and trigger the else part below.
             */

            try {
                if (HistoryJobEntity.HISTORY_JOB_TYPE.equals(deadLetterJob.getJobType())) {
                    managementService.moveDeadLetterJobToHistoryJob(deadLetterJob.getId(), cmmnEngineConfiguration.getAsyncExecutorNumberOfRetries());

View on GitHub (pinned to d6d39ce1c6)