flowable/flowable-engine · error · FlowableObjectNotFoundException
Could not find a dead letter job with id
Error message
Could not find a dead letter job with id '${jobId}'. What it means
Flowable's REST job-resource re-throws FlowableObjectNotFoundException with this message when the dead-letter job action could not be performed because the job does not exist (or is no longer a dead-letter job). It wraps any FlowableObjectNotFoundException from the underlying managementService call to give consistent error messaging across the REST API. The Job.class entity type is attached so clients know which entity was not found.
Solutions
- Re-fetch the dead-letter job list (GET /management/deadletter-jobs) and confirm the jobId exists before calling the action
- Check whether the job was already moved out of the dead-letter table (it may now be an executable/history job); use the corresponding resource instead
- Correct the jobId in the client/script (check for truncation or whitespace)
- Handle HTTP 404 from the REST call gracefully instead of treating it as a fatal failure
Example fix
// before
given().post("/management/deadletter-jobs/" + staleJobId);
// after
Job job = given().get("/management/deadletter-jobs/" + staleJobId).as(Job.class); // 404 check
if (job != null) { given().post("/management/deadletter-jobs/" + staleJobId); } Defensive patterns
Strategy: try-catch
Validate before calling
// client-side pre-check against the REST API
boolean exists = fetch("/management/deadletter-jobs")
.filter(j -> j.id.equals(jobId))
.isPresent();
if (!exists) throw new IllegalStateException("dead letter job " + jobId + " no longer present"); Type guard
function isDeadLetterJob(job) { return job && typeof job.id === 'string' && job.id === jobId; } Try / catch
try {
restTemplate.postForEntity(base + "/management/deadletter-jobs/" + jobId, action, Void.class);
} catch (HttpClientErrorException.NotFound e) {
log.warn("Dead letter job {} already gone; re-listing", jobId);
refreshDeadLetterJobs();
} Prevention
- Always resolve the job id from a fresh GET /management/deadletter-jobs immediately before acting
- Treat 404 on the action as 'already moved' rather than a failure in retry loops
- Avoid caching dead-letter job ids across retries or long-running batches
When it happens
Trigger: Calling POST /management/deadletter-jobs/{jobId} (executeDeadLetterJobAction) with an action such as MOVE to executable job when no dead-letter job with that jobId exists, the id is mistyped, or the job was already moved/executed and removed from the dead-letter table before the call.
Common situations: Stale job ids from a previous listing after a retry job was moved back to executable; racing with an admin UI that already moved the job; polling code that retries a move twice; typo'd or trimmed jobId from a script.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- Batch part with id ' ' does not have a batch part document.
- Batch with id ' ' does not have a batch document.
- Could not find a batch with id
- Could not find a case instance with id
- Could not find a case instance with id
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/b7ad59a3e7148d37.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-rest/src/main/java/org/flowable/rest/service/api/management/JobResource.java:342
if (MOVE_ACTION.equals(actionRequest.getAction())) {
/*
* 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(), processEngineConfiguration.getAsyncExecutorNumberOfRetries());
} else {
managementService.moveDeadLetterJobToExecutableJob(deadLetterJob.getId(), processEngineConfiguration.getAsyncExecutorNumberOfRetries());
}
} catch (FlowableObjectNotFoundException e) {
// Re-throw to have consistent error-messaging across REST-API
throw new FlowableObjectNotFoundException("Could not find a dead letter job with id '" + jobId + "'.", Job.class);
}
} else if (MOVE_TO_HISTORY_JOB_ACTION.equals(actionRequest.getAction())) {
try {
managementService.moveDeadLetterJobToHistoryJob(deadLetterJob.getId(), processEngineConfiguration.getAsyncHistoryExecutorNumberOfRetries());
} catch (FlowableObjectNotFoundException e) {
// Re-throw to have consistent error-messaging across REST-api
throw new FlowableObjectNotFoundException("Could not find a dead letter job with id '" + jobId + "'.", Job.class);
}
}
}
}
View on GitHub (pinned to d6d39ce1c6)