{"record":{"id":"1d44613296921cd2","repo":"flowable/flowable-engine","slug":"invalid-action-only-move-or-movetohistoryjob-1d4461","errorCode":null,"errorMessage":"Invalid action, only 'move' or 'moveToHistoryJob' is supported.","messagePattern":"Invalid action, only 'move' or 'moveToHistoryJob' is supported\\.","errorType":"http","errorClass":"FlowableIllegalArgumentException","httpStatus":400,"severity":"error","filePath":"modules/flowable-rest/src/main/java/org/flowable/rest/service/api/management/JobResource.java","lineNumber":319,"sourceCode":"                managementService.rescheduleTimeDateJob(job.getId(), actionRequest.getDueDate());\n            } catch (FlowableObjectNotFoundException e) {\n                // Re-throw to have consistent error-messaging across REST-api\n                throw new FlowableObjectNotFoundException(\"Could not find a timer job with id '\" + jobId + \"'.\", Job.class);\n            }\n        }\n    }\n    \n    @ApiOperation(value = \"Move a single deadletter job. Accepts 'move' and 'moveToHistoryJob' as action.\", tags = { \"Jobs\" }, code = 204)\n    @ApiResponses(value = {\n            @ApiResponse(code = 204, message = \"Indicates the dead letter job was moved. Response-body is intentionally empty.\"),\n            @ApiResponse(code = 404, message = \"Indicates the requested job was not found.\"),\n            @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.\")\n    })\n    @PostMapping(\"/management/deadletter-jobs/{jobId}\")\n    @ResponseStatus(HttpStatus.NO_CONTENT)\n    public void executeDeadLetterJobAction(@ApiParam(name = \"jobId\") @PathVariable String jobId, @RequestBody RestActionRequest actionRequest) {\n        if (actionRequest == null || !(MOVE_ACTION.equals(actionRequest.getAction()) || MOVE_TO_HISTORY_JOB_ACTION.equals(actionRequest.getAction()))) {\n            throw new FlowableIllegalArgumentException(\"Invalid action, only 'move' or 'moveToHistoryJob' is supported.\");\n        }\n        \n        Job deadLetterJob = getDeadLetterJobById(jobId);\n\n        if (MOVE_ACTION.equals(actionRequest.getAction())) {\n\n            /*\n             * Note that the jobType is checked to know which kind of move that needs to be done.\n             * The MOVE_TO_HISTORY_JOB_ACTION allows to specifically force the move to a history job and trigger the else part below.\n             */\n\n            try {\n                if (HistoryJobEntity.HISTORY_JOB_TYPE.equals(deadLetterJob.getJobType())) {\n                    managementService.moveDeadLetterJobToHistoryJob(deadLetterJob.getId(), processEngineConfiguration.getAsyncExecutorNumberOfRetries());\n\n                } else {\n                    managementService.moveDeadLetterJobToExecutableJob(deadLetterJob.getId(), processEngineConfiguration.getAsyncExecutorNumberOfRetries());\n","sourceCodeStart":301,"sourceCodeEnd":337,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/management/JobResource.java#L301-L337","documentation":"FlowableIllegalArgumentException thrown by POST /management/deadletter-jobs/{jobId} when the action is not 'move' or 'moveToHistoryJob'. The deadletter-job endpoint accepts a different action set than the timer-jobs endpoint, and the request body or action value failing this check is rejected before any job is loaded.","triggerScenarios":"POST /management/deadletter-jobs/{jobId} with (a) missing/empty body, (b) no 'action' field, or (c) action other than 'move' or 'moveToHistoryJob' — e.g. reusing 'reschedule' from the timer-jobs endpoint, which is not valid for deadletter jobs.","commonSituations":"Developers reusing the timer-job client code against deadletter jobs (sending 'reschedule'), typos like 'movetohistoryjob', missing JSON body, or copying examples for an older Flowable version with a different action vocabulary.","solutions":["Use {\"action\":\"move\"} to move the deadletter job back to the executable job table","Use {\"action\":\"moveToHistoryJob\"} to move it to the history (timer) job table","Remove 'reschedule' — it is only valid on /management/timer-jobs, not deadletter jobs","Check action spelling/casing against the constants MOVE_ACTION and MOVE_TO_HISTORY_JOB_ACTION"],"exampleFix":"// before\nPOST /management/deadletter-jobs/42 {\"action\":\"reschedule\",\"dueDate\":\"...\"}\n// after\nPOST /management/deadletter-jobs/42 {\"action\":\"move\"}","handlingStrategy":"validation","validationCode":"const DEADLETTER_ACTIONS = ['move', 'moveToHistoryJob'];\nif (!body || !DEADLETTER_ACTIONS.includes(body.action)) throw new Error(\"deadletter action must be 'move' or 'moveToHistoryJob'\");","typeGuard":"function isDeadLetterJobAction(a) { return a === 'move' || a === 'moveToHistoryJob'; }","tryCatchPattern":"try { await post(`/management/deadletter-jobs/${id}`, body); } catch (e) { if (e.status === 400) fixDeadLetterPayload(e); else throw e; }","preventionTips":["Never reuse timer-jobs action payloads (reschedule) for deadletter jobs","Validate the action against the deadletter-specific allowed set","Send a JSON body with Content-Type: application/json on every action call"],"tags":["rest-api","validation","flowable","deadletter-jobs"],"backgroundTag":"invalid-enum-value","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-18T11:17:12.947Z"}