flowable/flowable-engine · error · FlowableIllegalArgumentException
Cannot move a history job to an executable job
Error message
Cannot move a history job to an executable job
What it means
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.
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
Example fix
// before
for (DeadLetterJobEntity job : deadLetterJobs) {
jobManager.moveDeadLetterJobToExecutableJob(job, 3);
}
// after
for (DeadLetterJobEntity job : deadLetterJobs) {
if (!HistoryJobEntity.HISTORY_JOB_TYPE.equals(job.getJobType())) {
jobManager.moveDeadLetterJobToExecutableJob(job, 3);
}
} Defensive patterns
Strategy: validation
Validate before calling
if (HistoryJobEntity.HISTORY_JOB_TYPE.equals(job.getJobType())) { log.info("Skipping history job {} in executable retry", job.getId()); return; } Type guard
boolean isExecutableDeadLetterJob(DeadLetterJobEntity d) { return d != null && !HistoryJobEntity.HISTORY_JOB_TYPE.equals(d.getJobType()); } Try / catch
try { jobManager.moveDeadLetterJobToExecutableJob(job, retries); } catch (FlowableIllegalArgumentException e) { log.warn("Cannot move job {} to executable: {}", job.getId(), e.getMessage()); } Prevention
- Exclude history jobs from bulk executable-retry loops
- Branch on jobType before choosing a move method
- Keep retry tooling aware of history job semantics
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- Can only move a history job to a history job
- can't clear configuration beans
- can't search values in configuration beans
- Cannot change fixed value with
- Cannot move the job to deadletter: the job is not a timer…
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/23e7e8d92a77e3b0.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/asyncexecutor/DefaultJobManager.java:269
return deadLetterJob;
}
protected void sendMoveToDeadletterEvent(JobInfo job) {
FlowableEventDispatcher eventDispatcher = jobServiceConfiguration.getEventDispatcher();
if (eventDispatcher != null && eventDispatcher.isEnabled()) {
eventDispatcher.dispatchEvent(FlowableJobEventBuilder.createEntityEvent(
FlowableEngineEventType.JOB_MOVED_TO_DEADLETTER, job), jobServiceConfiguration.getEngineName());
}
}
@Override
public Job moveDeadLetterJobToExecutableJob(DeadLetterJobEntity deadLetterJobEntity, int retries) {
if (deadLetterJobEntity == null) {
throw new FlowableIllegalArgumentException("Null job provided");
}
if (HistoryJobEntity.HISTORY_JOB_TYPE.equals(deadLetterJobEntity.getJobType())) {
throw new FlowableIllegalArgumentException("Cannot move a history job to an executable job");
}
if (Job.JOB_TYPE_EXTERNAL_WORKER.equals(deadLetterJobEntity.getJobType())) {
ExternalWorkerJobEntity externalWorkerJob = createExternalWorkerJobFromOtherJob(deadLetterJobEntity);
externalWorkerJob.setRetries(retries);
boolean insertSuccessful = jobServiceConfiguration.getExternalWorkerJobEntityManager().insertExternalWorkerJobEntity(externalWorkerJob);
if (insertSuccessful) {
jobServiceConfiguration.getDeadLetterJobEntityManager().delete(deadLetterJobEntity);
return externalWorkerJob;
}
} else {
JobEntity executableJob = createExecutableJobFromOtherJob(deadLetterJobEntity);
executableJob.setRetries(retries);
boolean insertSuccessful = jobServiceConfiguration.getJobEntityManager().insertJobEntity(executableJob);
if (insertSuccessful) {
jobServiceConfiguration.getDeadLetterJobEntityManager().delete(deadLetterJobEntity);
triggerExecutorIfNeeded(executableJob);
return executableJob;View on GitHub (pinned to d6d39ce1c6)