{"record":{"id":"70e82f1b98c764b2","repo":"flowable/flowable-engine","slug":"async","errorCode":null,"errorMessage":"Async ","messagePattern":"Async ","errorType":"exception","errorClass":"FlowableException","httpStatus":null,"severity":"error","filePath":"modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/asyncexecutor/DefaultJobManager.java","lineNumber":588,"sourceCode":"    protected void executeHistoryJobHandler(HistoryJobEntity historyJobEntity) {\n        Map<String, HistoryJobHandler> jobHandlers = jobServiceConfiguration.getHistoryJobHandlers();\n        if (historyJobEntity.getJobHandlerType() != null) {\n            if (jobHandlers != null) {\n                HistoryJobHandler jobHandler = jobHandlers.get(historyJobEntity.getJobHandlerType());\n                if (jobHandler != null) {\n                    jobHandler.execute(historyJobEntity, historyJobEntity.getJobHandlerConfiguration(), getCommandContext(), jobServiceConfiguration);\n                } else {\n                    throw new FlowableException(\"No history job handler registered for type \" + historyJobEntity.getJobHandlerType() +\n                                    \" in job config for engine: \" + jobServiceConfiguration.getEngineName() + \" for \" + historyJobEntity);\n                }\n                \n            } else {\n                throw new FlowableException(\"No history job handler registered for type \" + historyJobEntity.getJobHandlerType() + \n                                \" in job config for engine: \" + jobServiceConfiguration.getEngineName() + \" for \" + historyJobEntity);\n            }\n            \n        } else {\n            throw new FlowableException(\"Async \" + historyJobEntity + \" has no job handler type in job config for engine: \" + jobServiceConfiguration.getEngineName());\n        }\n    }\n\n    protected boolean isValidTime(JobEntity timerEntity, Date newTimerDate, VariableScope variableScope) {\n        BusinessCalendar businessCalendar = jobServiceConfiguration.getBusinessCalendarManager().getBusinessCalendar(\n                getBusinessCalendarName(timerEntity, variableScope));\n        return businessCalendar.validateDuedate(timerEntity.getRepeat(), timerEntity.getMaxIterations(), timerEntity.getEndDate(), newTimerDate);\n    }\n\n    protected void hintAsyncExecutor(JobEntity job) {\n        // Verify that correct properties have been set when the async executor will be hinted\n        if (job.getLockOwner() == null || job.getLockExpirationTime() == null) {\n            createAsyncJob(job, job.isExclusive());\n        }\n        createHintListeners(getAsyncExecutor(), job);\n    }\n\n    protected void createHintListeners(AsyncExecutor asyncExecutor, JobInfoEntity job) {","sourceCodeStart":570,"sourceCodeEnd":606,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/asyncexecutor/DefaultJobManager.java#L570-L606","documentation":"executeHistoryJobHandler requires a non-null jobHandlerType on the HistoryJobEntity. When it is null, Flowable throws this FlowableException prefixed with 'Async', stating the history job has no job handler type. The engine cannot dispatch an async history job without knowing which handler processes it.","triggerScenarios":"A HistoryJobEntity (ACT_RU_HISTORY_JOB row) with NULL JOB_HANDLER_TYPE reaches executeHistoryJob -> executeHistoryJobHandler.","commonSituations":"History jobs inserted by custom code or migration scripts without the handler type; direct manipulation of history job tables; bugs in custom HistoryJobEntity creation bypassing the standard HistoryJobService.createHistoryJob().","solutions":["Create history jobs only through HistoryJobService.createHistoryJob(), which sets the default handler type.","Set setJobHandlerType(...) explicitly on any custom HistoryJobEntity before insertion.","Repair existing rows: populate JOB_HANDLER_TYPE (usually 'async-history') or delete the orphan row.","Audit migration/ETL scripts touching ACT_RU_HISTORY_JOB for the handler-type column."],"exampleFix":"// before\nHistoryJobEntity job = historyService.createHistoryJob();\n// handler type never set\n\n// after\nHistoryJobEntity job = historyService.createHistoryJob();\njob.setJobHandlerType(AsyncHistoryJobHandler.TYPE); // \"async-history\"","handlingStrategy":"validation","validationCode":"if (historyJob.getJobHandlerType() == null || historyJob.getJobHandlerType().isEmpty()) {\n    throw new IllegalStateException(\"HistoryJob \" + historyJob.getId() + \" has no jobHandlerType\");\n}","typeGuard":"boolean hasHistoryHandlerType(HistoryJobEntity job) { return job.getJobHandlerType() != null && !job.getJobHandlerType().isEmpty(); }","tryCatchPattern":"try { historyJobManager.executeHistoryJob(job); } catch (FlowableException e) { if (e.getMessage().endsWith(\"has no job handler type\")) { repairOrDeleteHistoryJobRow(job.getId()); } else throw e; }","preventionTips":["Create history jobs only via HistoryJobService.createHistoryJob().","Set handler type explicitly on any custom history job creation.","Audit ACT_RU_HISTORY_JOB rows for NULL JOB_HANDLER_TYPE after migrations."],"tags":["flowable","async-history","null-field","data-integrity"],"backgroundTag":"missing-required-argument","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"}