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

  1. Re-fetch the dead-letter job list (GET /management/deadletter-jobs) and confirm the jobId exists before calling the action
  2. 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
  3. Correct the jobId in the client/script (check for truncation or whitespace)
  4. 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

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


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)