flowable/flowable-engine · error · FlowableObjectNotFoundException

Could not find a timer job with id

Error message

Could not find a timer job with id '${jobId}'.

What it means

FlowableObjectNotFoundException thrown when action='move' is requested for a timer job id that does not exist. The REST layer first loads the job via getTimerJobById (which throws this if the id is unknown), and managementService.moveTimerToExecutableJob failures are re-thrown with this exact message for consistent REST error messaging.

Solutions

  1. Verify the job id exists by GET /management/timer-jobs/{jobId} before moving
  2. Confirm the id belongs to a TIMER job, not a deadletter/suspended/failed job — use the matching endpoint
  3. Re-query the timer job list if the id is stale; the job may have already executed and been removed
  4. Check tenant/database: the REST app must point at the same DB where the job was created

Example fix

// before
moveTimerJob('1234'); // id from an old snapshot
// after
const job = await fetch(`/management/timer-jobs/1234`).then(r => { if (!r.ok) throw new Error('gone'); return r.json(); });
if (job) moveTimerJob(job.id);
Defensive patterns

Strategy: try-catch

Validate before calling

const res = await fetch(`/management/timer-jobs/${jobId}`);
if (res.status === 404) throw new Error(`timer job ${jobId} no longer exists`);

Try / catch

try { await moveTimerJob(jobId); } catch (e) { if (e.message.includes('Could not find a timer job')) await refreshJobListAndRetry(); else throw e; }

Prevention

When it happens

Trigger: POST /management/timer-jobs/{jobId} with {"action":"move"} where jobId is not an existing timer job: deleted job, already-executed timer job, a job id of a different job type (e.g. a deadletter or suspended job id), or an id from a different database/tenant.

Common situations: Stale client-side caches of job ids, timer job fired and removed between listing and acting, confusing timer-jobs with deadletter-jobs endpoints, multi-database setups pointing the client at the wrong schema.

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/c97d099f98f72217. Report an issue: GitHub.

Appendix: source

Thrown at modules/flowable-rest/src/main/java/org/flowable/rest/service/api/management/JobResource.java:294

            @ApiResponse(code = 204, message = "Indicates the timer job action was executed. Response-body is intentionally empty."),
            @ApiResponse(code = 404, message = "Indicates the requested job was not found."),
            @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.")
    })
    @PostMapping("/management/timer-jobs/{jobId}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void executeTimerJobAction(@ApiParam(name = "jobId") @PathVariable String jobId, @RequestBody TimerJobActionRequest actionRequest) {
        if (actionRequest == null || !(MOVE_ACTION.equals(actionRequest.getAction()) || RESCHEDULE_ACTION.equals(actionRequest.getAction()))) {
            throw new FlowableIllegalArgumentException("Invalid action, only 'move' or 'reschedule' are supported.");
        }
        
        Job job = getTimerJobById(jobId);

        if (MOVE_ACTION.equals(actionRequest.getAction())) {
            try {
                managementService.moveTimerToExecutableJob(job.getId());
            } catch (FlowableObjectNotFoundException e) {
                // Re-throw to have consistent error-messaging across REST-api
                throw new FlowableObjectNotFoundException("Could not find a timer job with id '" + jobId + "'.", Job.class);
            }
        } else if (RESCHEDULE_ACTION.equals(actionRequest.getAction())) {
            if (actionRequest.getDueDate() == null) {
                throw new FlowableIllegalArgumentException("Invalid reschedule timer action. Reschedule timer actions must have a valid due date.");
            }
            try {
                managementService.rescheduleTimeDateJob(job.getId(), actionRequest.getDueDate());
            } catch (FlowableObjectNotFoundException e) {
                // Re-throw to have consistent error-messaging across REST-api
                throw new FlowableObjectNotFoundException("Could not find a timer job with id '" + jobId + "'.", Job.class);
            }
        }
    }
    
    @ApiOperation(value = "Move a single deadletter job. Accepts 'move' and 'moveToHistoryJob' as action.", tags = { "Jobs" }, code = 204)
    @ApiResponses(value = {
            @ApiResponse(code = 204, message = "Indicates the dead letter job was moved. Response-body is intentionally empty."),
            @ApiResponse(code = 404, message = "Indicates the requested job was not found."),

View on GitHub (pinned to d6d39ce1c6)