flowable/flowable-engine · error · FlowableObjectNotFoundException

Could not find a dead letter job(s) with id(s) {}

Error message

Could not find a dead letter job(s) with id(s) {}

What it means

After validating the action, executeDeadLetterJobAction counts the requested job ids among dead letter jobs; if some ids don't exist it removes found ids and throws FlowableObjectNotFoundException listing the missing id(s).

Source

Thrown at modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/management/JobCollectionResource.java:210

    @ApiResponses(value = {
            @ApiResponse(code = 204, message = "Indicates the dead letter jobs where moved. Response-body is intentionally empty."),
            @ApiResponse(code = 500, message = "Indicates the an exception occurred while executing the job. The status-description contains additional detail about the error. The full error-stacktrace can be fetched later on if needed.")
    })
    @ResponseStatus(value = HttpStatus.NO_CONTENT)
    @PostMapping("/cmmn-management/deadletter-jobs")
    public void executeDeadLetterJobAction(@RequestBody BulkMoveDeadLetterActionRequest actionRequest) {
        if (actionRequest == null || !(MOVE_ACTION.equals(actionRequest.getAction()) || MOVE_TO_HISTORY_JOB_ACTION.equals(actionRequest.getAction()))) {
            throw new FlowableIllegalArgumentException("Invalid action, only 'move' or 'moveToHistoryJob' is supported.");
        }

        Collection<String> jobIds = actionRequest.getJobIds();
        long existingJobIdCount = managementService.createDeadLetterJobQuery().jobIds(jobIds).count();
        if (jobIds.size() != existingJobIdCount) {
            List<Job> foundJobs = managementService.createDeadLetterJobQuery().jobIds(jobIds).list();
            for (Job job : foundJobs) {
                jobIds.remove(job.getId());
            }
            throw new FlowableObjectNotFoundException(
                    "Could not find a dead letter job(s) with id(s) {" + jobIds.stream().collect(Collectors.joining(",")) + "}", Job.class);
        }
        if (MOVE_ACTION.equals(actionRequest.getAction())) {
            if (restApiInterceptor != null) {
                restApiInterceptor.bulkMoveDeadLetterJobs(actionRequest.getJobIds(), MOVE_ACTION);
            }
            managementService.bulkMoveDeadLetterJobs(actionRequest.getJobIds(), cmmnEngineConfiguration.getAsyncExecutorNumberOfRetries());
        } else if (MOVE_TO_HISTORY_JOB_ACTION.equals(actionRequest.getAction())) {
            if (restApiInterceptor != null) {
                restApiInterceptor.bulkMoveDeadLetterJobs(actionRequest.getJobIds(), MOVE_TO_HISTORY_JOB_ACTION);
            }
            managementService.bulkMoveDeadLetterJobsToHistoryJobs(actionRequest.getJobIds(), cmmnEngineConfiguration.getAsyncHistoryExecutorNumberOfRetries());
        }
    }
}

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Re-query /cmmn-management/deadletter-jobs and build jobIds only from the current response.
  2. Parse the error message to remove the missing ids and retry with valid ones only.
  3. Make the move idempotent: treat 'job not found' for already-moved jobs as success.

Example fix

// before
{"action":"move","jobIds":["1","2","stale-id"]}
// after
{"action":"move","jobIds":["1","2"]} // ids refreshed from deadletter-jobs list
Defensive patterns

Strategy: validation

Validate before calling

const ids = (await listDeadLetterJobs()).map(j => j.id);
const requested = ids.filter(id => wanted.has(id));
if (requested.length === 0) throw new Error("no valid dead letter job ids to move");

Try / catch

try { bulkMove(ids) } catch (e) { if (e.status === 404) { retryWith(parseMissingIds(e.message)); } else throw e; }

Prevention

When it happens

Trigger: POST /cmmn-management/deadletter-jobs with jobIds containing at least one id that is not (or no longer) a dead letter job — mixed job types, already-moved jobs, typos, or duplicate/empty ids.

Common situations: Bulk-move built from a cached job list where some jobs were already handled; copying ids from BPM engine lists into CMMN requests; retrying a move that partially succeeded earlier.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/ce4cc74866db3065. Report an issue: GitHub.