{"record":{"id":"498e746b3db4ae03","repo":"flowable/flowable-engine","slug":"timer-job-with-id-jobid-does-not-have-an-exce","errorCode":null,"errorMessage":"Timer job with id '${jobId}' does not have an exception stacktrace.","messagePattern":"Timer job with id '(.+?)' does not have an exception stacktrace\\.","errorType":"http","errorClass":"FlowableObjectNotFoundException","httpStatus":404,"severity":"info","filePath":"modules/flowable-rest/src/main/java/org/flowable/rest/service/api/management/JobExceptionStacktraceResource.java","lineNumber":70,"sourceCode":"        }\n\n        response.setContentType(\"text/plain\");\n        return stackTrace;\n    }\n\n    @ApiOperation(value = \"Get the exception stacktrace for a timer job\", tags = { \"Jobs\" })\n    @ApiResponses(value = {\n            @ApiResponse(code = 200, message = \"Indicates the requested job was not found and the stacktrace has been returned. The response contains the raw stacktrace and always has a Content-type of text/plain.\"),\n            @ApiResponse(code = 404, message = \"Indicates the requested job was not found or the job does not have an exception stacktrace. Status-description contains additional information about the error.\")\n    })\n    @GetMapping(\"/management/timer-jobs/{jobId}/exception-stacktrace\")\n    public String getTimerJobStacktrace(@ApiParam(name = \"jobId\") @PathVariable String jobId, HttpServletResponse response) {\n        Job job = getTimerJobById(jobId);\n\n        String stackTrace = managementService.getTimerJobExceptionStacktrace(job.getId());\n\n        if (stackTrace == null) {\n            throw new FlowableObjectNotFoundException(\"Timer job with id '\" + job.getId() + \"' does not have an exception stacktrace.\", String.class);\n        }\n\n        response.setContentType(\"text/plain\");\n        return stackTrace;\n    }\n\n    @ApiOperation(value = \"Get the exception stacktrace for a suspended job\", tags = { \"Jobs\" })\n    @ApiResponses(value = {\n            @ApiResponse(code = 200, message = \"Indicates the requested job was not found and the stacktrace has been returned. The response contains the raw stacktrace and always has a Content-type of text/plain.\"),\n            @ApiResponse(code = 404, message = \"Indicates the requested job was not found or the job does not have an exception stacktrace. Status-description contains additional information about the error.\")\n    })\n    @GetMapping(\"/management/suspended-jobs/{jobId}/exception-stacktrace\")\n    public String getSuspendedJobStacktrace(@ApiParam(name = \"jobId\") @PathVariable String jobId, HttpServletResponse response) {\n        Job job = getSuspendedJobById(jobId);\n\n        String stackTrace = managementService.getSuspendedJobExceptionStacktrace(job.getId());\n\n        if (stackTrace == null) {","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/management/JobExceptionStacktraceResource.java#L52-L88","documentation":"Same guard as the plain-job variant but for timer jobs: getTimerJobStacktrace throws FlowableObjectNotFoundException when managementService.getTimerJobExceptionStacktrace returns null, i.e. the timer job has no recorded exception.","triggerScenarios":"GET /management/timer-jobs/{jobId}/exception-stacktrace for a timer job that has not failed (never executed, or executing successfully).","commonSituations":"Polling stacktraces for all timer jobs in dashboards, timer jobs that were rescheduled (clearing prior failure data), or confusion between timer-jobs and deadletter-jobs endpoints for failed jobs.","solutions":["Request stacktraces only for timer jobs that show a non-null exceptionMessage.","For failed timer jobs, look in /management/deadletter-jobs/{id}/exception-stacktrace instead.","Handle 404 as 'job healthy / no stacktrace' in monitoring code.","Re-query the timer job to confirm it still exists after rescheduling."],"exampleFix":"// before\ncurl http://localhost:8080/flowable-rest/management/timer-jobs/3/exception-stacktrace\n// after: check failure state first\nJOB=$(curl http://localhost:8080/flowable-rest/management/timer-jobs/3)\necho $JOB | jq -r '.exceptionMessage // empty' | grep -q . && \\\n  curl http://localhost:8080/flowable-rest/management/timer-jobs/3/exception-stacktrace","handlingStrategy":"try-catch","validationCode":"const job = await fetch(`${base}/management/timer-jobs/${jobId}`).then(r => r.json());\nif (!job.exceptionMessage) return null; // healthy timer job","typeGuard":"function hasRecordedFailure(job) { return !!job && typeof job.exceptionMessage === 'string' && job.exceptionMessage.length > 0; }","tryCatchPattern":"try {\n  const trace = await getTimerJobStacktrace(jobId);\n} catch (e) {\n  if (e.status === 404) return null;\n  throw e;\n}","preventionTips":["Gate stacktrace fetches on the job's exceptionMessage field.","Remember rescheduling clears prior failure details.","Use the deadletter endpoint for terminal failures."],"tags":["rest-api","timer-job","stacktrace","not-found"],"backgroundTag":"empty-result-set","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}