flowable/flowable-engine · error · FlowableIllegalArgumentException
Cannot move the job to deadletter: the job is not a timer…
Error message
Cannot move the job to deadletter: the job is not a timer, async job or external worker job
What it means
moveJobToDeadLetterJob moves a timer, async, or external-worker job to the dead-letter table after repeated failures. If the job object is none of the recognized types (TimerJobEntity, JobEntity, ExternalWorkerJobEntity, or their dead-letter variants handled earlier), the library throws FlowableIllegalArgumentException because dead-lettering that job type is unsupported.
Solutions
- Filter by job type before calling: only pass timer, async (JobEntity), or external-worker jobs
- Handle history jobs with moveDeadLetterJobToHistoryJob or dedicated history-job APIs instead
- Update custom failure handlers when new Flowable job types are introduced
Example fix
// before
jobManager.moveJobToDeadLetterJob(job); // job may be a history job
// after
if (job instanceof TimerJobEntity || job instanceof JobEntity || job instanceof ExternalWorkerJobEntity) {
jobManager.moveJobToDeadLetterJob(job);
} else {
// route other job types appropriately
} Defensive patterns
Strategy: type-guard
Validate before calling
if (!(job instanceof TimerJobEntity) && !(job instanceof JobEntity) && !(job instanceof ExternalWorkerJobEntity)) { throw new IllegalArgumentException("Unsupported job type for deadletter: " + job.getClass()); } Type guard
boolean canMoveToDeadLetter(JobInfo job) { return job instanceof TimerJobEntity || job instanceof JobEntity || job instanceof ExternalWorkerJobEntity; } Try / catch
try { jobManager.moveJobToDeadLetterJob(job); } catch (FlowableIllegalArgumentException e) { log.error("Job type not supported for deadletter: {}", job.getClass(), e); } Prevention
- Filter by job type before dead-lettering
- Keep failure handlers in sync with Flowable job types
- Route history jobs to their dedicated APIs
When it happens
Trigger: Calling moveJobToDeadLetterJob with a HistoryJobEntity, suspended-job wrapper, or other JobInfo subtype not handled by the instanceof chain; passing a custom Job implementation.
Common situations: Custom failure-handling code that forwards every failed job to the dead-letter path without filtering by type; framework upgrades adding new job types that old handler code funnels into moveJobToDeadLetterJob.
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
- Null job provided
- App resource is not of type AppModel
- Can only use a collection of String elements for…
- Cannot convert event payload
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/09ca07b2061f67bd.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/asyncexecutor/DefaultJobManager.java:248
}
jobServiceConfiguration.getSuspendedJobEntityManager().delete(job);
return activatedJob;
}
@Override
public DeadLetterJobEntity moveJobToDeadLetterJob(AbstractRuntimeJobEntity job) {
DeadLetterJobEntity deadLetterJob = createDeadLetterJobFromOtherJob(job);
jobServiceConfiguration.getDeadLetterJobEntityManager().insert(deadLetterJob);
if (job instanceof TimerJobEntity) {
jobServiceConfiguration.getTimerJobEntityManager().delete((TimerJobEntity) job);
} else if (job instanceof JobEntity) {
jobServiceConfiguration.getJobEntityManager().delete((JobEntity) job);
} else if (job instanceof ExternalWorkerJobEntity) {
jobServiceConfiguration.getExternalWorkerJobEntityManager().delete((ExternalWorkerJobEntity) job);
} else {
throw new FlowableIllegalArgumentException("Cannot move the job to deadletter: the job is not a timer, async job or external worker job");
}
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");
}View on GitHub (pinned to d6d39ce1c6)