flowable/flowable-engine · error · FlowableException
Async
Error message
Async
What it means
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.
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.
Example fix
// before HistoryJobEntity job = historyService.createHistoryJob(); // handler type never set // after HistoryJobEntity job = historyService.createHistoryJob(); job.setJobHandlerType(AsyncHistoryJobHandler.TYPE); // "async-history"
Defensive patterns
Strategy: validation
Validate before calling
if (historyJob.getJobHandlerType() == null || historyJob.getJobHandlerType().isEmpty()) {
throw new IllegalStateException("HistoryJob " + historyJob.getId() + " has no jobHandlerType");
} Type guard
boolean hasHistoryHandlerType(HistoryJobEntity job) { return job.getJobHandlerType() != null && !job.getJobHandlerType().isEmpty(); } Try / catch
try { historyJobManager.executeHistoryJob(job); } catch (FlowableException e) { if (e.getMessage().endsWith("has no job handler type")) { repairOrDeleteHistoryJobRow(job.getId()); } else throw e; } Prevention
- 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.
When it happens
Trigger: A HistoryJobEntity (ACT_RU_HISTORY_JOB row) with NULL JOB_HANDLER_TYPE reaches executeHistoryJob -> executeHistoryJobHandler.
Common situations: 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().
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- has no job handler type in job config for engine:
- Cannot get process definition for id for
- No history job handler registered for type
- A channel key detection value is required for inbound…
- A channel key detection value is required for the channel…
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/70e82f1b98c764b2.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-job-service/src/main/java/org/flowable/job/service/impl/asyncexecutor/DefaultJobManager.java:588
protected void executeHistoryJobHandler(HistoryJobEntity historyJobEntity) {
Map<String, HistoryJobHandler> jobHandlers = jobServiceConfiguration.getHistoryJobHandlers();
if (historyJobEntity.getJobHandlerType() != null) {
if (jobHandlers != null) {
HistoryJobHandler jobHandler = jobHandlers.get(historyJobEntity.getJobHandlerType());
if (jobHandler != null) {
jobHandler.execute(historyJobEntity, historyJobEntity.getJobHandlerConfiguration(), getCommandContext(), jobServiceConfiguration);
} else {
throw new FlowableException("No history job handler registered for type " + historyJobEntity.getJobHandlerType() +
" in job config for engine: " + jobServiceConfiguration.getEngineName() + " for " + historyJobEntity);
}
} else {
throw new FlowableException("No history job handler registered for type " + historyJobEntity.getJobHandlerType() +
" in job config for engine: " + jobServiceConfiguration.getEngineName() + " for " + historyJobEntity);
}
} else {
throw new FlowableException("Async " + historyJobEntity + " has no job handler type in job config for engine: " + jobServiceConfiguration.getEngineName());
}
}
protected boolean isValidTime(JobEntity timerEntity, Date newTimerDate, VariableScope variableScope) {
BusinessCalendar businessCalendar = jobServiceConfiguration.getBusinessCalendarManager().getBusinessCalendar(
getBusinessCalendarName(timerEntity, variableScope));
return businessCalendar.validateDuedate(timerEntity.getRepeat(), timerEntity.getMaxIterations(), timerEntity.getEndDate(), newTimerDate);
}
protected void hintAsyncExecutor(JobEntity job) {
// Verify that correct properties have been set when the async executor will be hinted
if (job.getLockOwner() == null || job.getLockExpirationTime() == null) {
createAsyncJob(job, job.isExclusive());
}
createHintListeners(getAsyncExecutor(), job);
}
protected void createHintListeners(AsyncExecutor asyncExecutor, JobInfoEntity job) {View on GitHub (pinned to d6d39ce1c6)