{"record":{"id":"23e7e8d92a77e3b0","repo":"flowable/flowable-engine","slug":"cannot-move-a-history-job-to-an-executable-job","errorCode":null,"errorMessage":"Cannot move a history job to an executable job","messagePattern":"Cannot move a history job to an executable job","errorType":"validation","errorClass":"FlowableIllegalArgumentException","httpStatus":null,"severity":"error","filePath":"modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/asyncexecutor/DefaultJobManager.java","lineNumber":269,"sourceCode":"        return deadLetterJob;\n    }\n\n    protected void sendMoveToDeadletterEvent(JobInfo job) {\n        FlowableEventDispatcher eventDispatcher = jobServiceConfiguration.getEventDispatcher();\n        if (eventDispatcher != null && eventDispatcher.isEnabled()) {\n            eventDispatcher.dispatchEvent(FlowableJobEventBuilder.createEntityEvent(\n                FlowableEngineEventType.JOB_MOVED_TO_DEADLETTER, job), jobServiceConfiguration.getEngineName());\n        }\n    }\n\n    @Override\n    public Job moveDeadLetterJobToExecutableJob(DeadLetterJobEntity deadLetterJobEntity, int retries) {\n        if (deadLetterJobEntity == null) {\n            throw new FlowableIllegalArgumentException(\"Null job provided\");\n        }\n\n        if (HistoryJobEntity.HISTORY_JOB_TYPE.equals(deadLetterJobEntity.getJobType())) {\n            throw new FlowableIllegalArgumentException(\"Cannot move a history job to an executable job\");\n        }\n\n        if (Job.JOB_TYPE_EXTERNAL_WORKER.equals(deadLetterJobEntity.getJobType())) {\n            ExternalWorkerJobEntity externalWorkerJob = createExternalWorkerJobFromOtherJob(deadLetterJobEntity);\n            externalWorkerJob.setRetries(retries);\n            boolean insertSuccessful = jobServiceConfiguration.getExternalWorkerJobEntityManager().insertExternalWorkerJobEntity(externalWorkerJob);\n            if (insertSuccessful) {\n                jobServiceConfiguration.getDeadLetterJobEntityManager().delete(deadLetterJobEntity);\n                return externalWorkerJob;\n            }\n        } else {\n            JobEntity executableJob = createExecutableJobFromOtherJob(deadLetterJobEntity);\n            executableJob.setRetries(retries);\n            boolean insertSuccessful = jobServiceConfiguration.getJobEntityManager().insertJobEntity(executableJob);\n            if (insertSuccessful) {\n                jobServiceConfiguration.getDeadLetterJobEntityManager().delete(deadLetterJobEntity);\n                triggerExecutorIfNeeded(executableJob);\n                return executableJob;","sourceCodeStart":251,"sourceCodeEnd":287,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/asyncexecutor/DefaultJobManager.java#L251-L287","documentation":"moveDeadLetterJobToExecutableJob only handles regular dead-letter jobs. History jobs are explicitly excluded: attempting to move a dead-letter job whose jobType equals HistoryJobEntity.HISTORY_JOB_TYPE throws FlowableIllegalArgumentException, since history jobs are managed by separate mechanisms.","triggerScenarios":"Calling moveDeadLetterJobToExecutableJob on a dead-letter job whose getJobType() returns \"history\", e.g. retrying all dead-letter jobs in a loop without filtering by type.","commonSituations":"Bulk retry utilities that iterate the dead-letter job table and hit history jobs that landed there via moveDeadLetterJobToHistoryJob; admin tooling assuming one uniform job type.","solutions":["Skip jobs where HistoryJobEntity.HISTORY_JOB_TYPE.equals(job.getJobType()) in retry loops","Use the history-job management APIs for history jobs instead","Filter dead-letter queries to exclude history job type before bulk retry"],"exampleFix":"// before\nfor (DeadLetterJobEntity job : deadLetterJobs) {\n    jobManager.moveDeadLetterJobToExecutableJob(job, 3);\n}\n// after\nfor (DeadLetterJobEntity job : deadLetterJobs) {\n    if (!HistoryJobEntity.HISTORY_JOB_TYPE.equals(job.getJobType())) {\n        jobManager.moveDeadLetterJobToExecutableJob(job, 3);\n    }\n}","handlingStrategy":"validation","validationCode":"if (HistoryJobEntity.HISTORY_JOB_TYPE.equals(job.getJobType())) { log.info(\"Skipping history job {} in executable retry\", job.getId()); return; }","typeGuard":"boolean isExecutableDeadLetterJob(DeadLetterJobEntity d) { return d != null && !HistoryJobEntity.HISTORY_JOB_TYPE.equals(d.getJobType()); }","tryCatchPattern":"try { jobManager.moveDeadLetterJobToExecutableJob(job, retries); } catch (FlowableIllegalArgumentException e) { log.warn(\"Cannot move job {} to executable: {}\", job.getId(), e.getMessage()); }","preventionTips":["Exclude history jobs from bulk executable-retry loops","Branch on jobType before choosing a move method","Keep retry tooling aware of history job semantics"],"tags":["flowable","job-scheduling","history-job","unsupported-operation"],"backgroundTag":"unsupported-operation","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"}