{"record":{"id":"c97d099f98f72217","repo":"flowable/flowable-engine","slug":"could-not-find-a-timer-job-with-id-jobid-c97d09","errorCode":null,"errorMessage":"Could not find a timer job with id '${jobId}'.","messagePattern":"Could not find a timer job with id '(.+?)'\\.","errorType":"http","errorClass":"FlowableObjectNotFoundException","httpStatus":404,"severity":"error","filePath":"modules/flowable-rest/src/main/java/org/flowable/rest/service/api/management/JobResource.java","lineNumber":294,"sourceCode":"            @ApiResponse(code = 204, message = \"Indicates the timer job action was executed. Response-body is intentionally empty.\"),\n            @ApiResponse(code = 404, message = \"Indicates the requested job was not found.\"),\n            @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.\")\n    })\n    @PostMapping(\"/management/timer-jobs/{jobId}\")\n    @ResponseStatus(HttpStatus.NO_CONTENT)\n    public void executeTimerJobAction(@ApiParam(name = \"jobId\") @PathVariable String jobId, @RequestBody TimerJobActionRequest actionRequest) {\n        if (actionRequest == null || !(MOVE_ACTION.equals(actionRequest.getAction()) || RESCHEDULE_ACTION.equals(actionRequest.getAction()))) {\n            throw new FlowableIllegalArgumentException(\"Invalid action, only 'move' or 'reschedule' are supported.\");\n        }\n        \n        Job job = getTimerJobById(jobId);\n\n        if (MOVE_ACTION.equals(actionRequest.getAction())) {\n            try {\n                managementService.moveTimerToExecutableJob(job.getId());\n            } catch (FlowableObjectNotFoundException e) {\n                // Re-throw to have consistent error-messaging across REST-api\n                throw new FlowableObjectNotFoundException(\"Could not find a timer job with id '\" + jobId + \"'.\", Job.class);\n            }\n        } else if (RESCHEDULE_ACTION.equals(actionRequest.getAction())) {\n            if (actionRequest.getDueDate() == null) {\n                throw new FlowableIllegalArgumentException(\"Invalid reschedule timer action. Reschedule timer actions must have a valid due date.\");\n            }\n            try {\n                managementService.rescheduleTimeDateJob(job.getId(), actionRequest.getDueDate());\n            } catch (FlowableObjectNotFoundException e) {\n                // Re-throw to have consistent error-messaging across REST-api\n                throw new FlowableObjectNotFoundException(\"Could not find a timer job with id '\" + jobId + \"'.\", Job.class);\n            }\n        }\n    }\n    \n    @ApiOperation(value = \"Move a single deadletter job. Accepts 'move' and 'moveToHistoryJob' as action.\", tags = { \"Jobs\" }, code = 204)\n    @ApiResponses(value = {\n            @ApiResponse(code = 204, message = \"Indicates the dead letter job was moved. Response-body is intentionally empty.\"),\n            @ApiResponse(code = 404, message = \"Indicates the requested job was not found.\"),","sourceCodeStart":276,"sourceCodeEnd":312,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/management/JobResource.java#L276-L312","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nmoveTimerJob('1234'); // id from an old snapshot\n// after\nconst job = await fetch(`/management/timer-jobs/1234`).then(r => { if (!r.ok) throw new Error('gone'); return r.json(); });\nif (job) moveTimerJob(job.id);","handlingStrategy":"try-catch","validationCode":"const res = await fetch(`/management/timer-jobs/${jobId}`);\nif (res.status === 404) throw new Error(`timer job ${jobId} no longer exists`);","typeGuard":null,"tryCatchPattern":"try { await moveTimerJob(jobId); } catch (e) { if (e.message.includes('Could not find a timer job')) await refreshJobListAndRetry(); else throw e; }","preventionTips":["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"],"tags":["rest-api","not-found","flowable","jobs"],"backgroundTag":"record-not-found","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-18T11:17:12.947Z"}