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
- Verify the job id exists by GET /management/timer-jobs/{jobId} before moving
- Confirm the id belongs to a TIMER job, not a deadletter/suspended/failed job — use the matching endpoint
- Re-query the timer job list if the id is stale; the job may have already executed and been removed
- 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
- Fetch the job immediately before acting on it; timer jobs can disappear once fired
- Use the correct endpoint per job type (timer vs deadletter vs suspended)
- Confirm the REST app points at the same database/tenant where the job lives
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
- 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/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)