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

  1. Create history jobs only through HistoryJobService.createHistoryJob(), which sets the default handler type.
  2. Set setJobHandlerType(...) explicitly on any custom HistoryJobEntity before insertion.
  3. Repair existing rows: populate JOB_HANDLER_TYPE (usually 'async-history') or delete the orphan row.
  4. 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

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


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)