flowable/flowable-engine · error · FlowableException
Historic case instance has not ended and can only be migrate
Error message
Historic case instance has not ended and can only be migrated with the regular case instance migrate method (migrateCaseInstance) for id ${caseInstanceId} What it means
migrateHistoricCaseInstance only accepts historic (ended) case instances. If the found historic case instance's state is not in CaseInstanceState.END_STATES, this FlowableException tells you to use the regular migrateCaseInstance method instead.
Source
Thrown at modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/CaseInstanceMigrationManagerImpl.java:232
CaseInstanceEntity caseInstance = caseInstanceEntityManager.findById(caseInstanceId);
if (caseInstance == null) {
throw new FlowableException("Cannot find the case to migrate, with id" + caseInstanceId);
}
CaseDefinition caseDefinitionToMigrateTo = resolveCaseDefinition(document, commandContext);
doMigrateCaseInstance(caseInstance, caseDefinitionToMigrateTo, document, commandContext);
}
@Override
public void migrateHistoricCaseInstance(String caseInstanceId, HistoricCaseInstanceMigrationDocument document, CommandContext commandContext) {
HistoricCaseInstanceEntityManager historicCaseInstanceEntityManager = CommandContextUtil.getHistoricCaseInstanceEntityManager(commandContext);
HistoricCaseInstanceEntity caseInstance = historicCaseInstanceEntityManager.findById(caseInstanceId);
if (caseInstance == null) {
throw new FlowableException("Cannot find the historic case instance to migrate, with id" + caseInstanceId);
}
if (!CaseInstanceState.END_STATES.contains(caseInstance.getState())) {
throw new FlowableException("Historic case instance has not ended and can only be migrated with the regular case instance migrate method (migrateCaseInstance) for id " + caseInstanceId);
}
CaseDefinition caseDefinitionToMigrateTo = resolveCaseDefinition(document, commandContext);
doMigrateHistoricCaseInstance(caseInstance, caseDefinitionToMigrateTo, document, commandContext);
}
@Override
public void migrateCaseInstancesOfCaseDefinition(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId, CaseInstanceMigrationDocument document, CommandContext commandContext) {
CaseDefinition caseDefinition = resolveCaseDefinition(caseDefinitionKey, caseDefinitionVersion, caseDefinitionTenantId, commandContext);
migrateCaseInstancesOfCaseDefinition(caseDefinition.getId(), document, commandContext);
}
@Override
public void migrateHistoricCaseInstancesOfCaseDefinition(String caseDefinitionKey, int caseDefinitionVersion, String caseDefinitionTenantId, HistoricCaseInstanceMigrationDocument document, CommandContext commandContext) {
CaseDefinition caseDefinition = resolveCaseDefinition(caseDefinitionKey, caseDefinitionVersion, caseDefinitionTenantId, commandContext);
migrateHistoricCaseInstancesOfCaseDefinition(caseDefinition.getId(), document, commandContext);
}
View on GitHub (pinned to d6d39ce1c6)
Solutions
- Use migrationManager.migrateCaseInstance(caseId, document, commandContext) for running cases
- Verify the case is finished first: historyService.createHistoricCaseInstanceQuery().caseInstanceId(id).finished().count() > 0
- If the case should be ended, ensure the case completes before migrating
Example fix
// before historyMigrationManager.migrateHistoricCaseInstance(runningCaseId, doc, commandContext); // after migrationManager.migrateCaseInstance(runningCaseId, doc, commandContext); // for active cases
Defensive patterns
Strategy: validation
Validate before calling
const hist = historyService.createHistoricCaseInstanceQuery().caseInstanceId(caseId).singleResult();
if (hist && !endStates.includes(hist.state)) throw new Error('Case ' + caseId + ' has not ended; use migrateCaseInstance'); Type guard
function hasEnded(h) { return h != null && ['completed','terminated','closed'].includes(h.state); } Try / catch
try { migrateHistoricCaseInstance(id, doc); } catch (e) { if (String(e.message).includes('has not ended and can only be migrated')) { return migrateCaseInstance(id, doc); } throw e; } Prevention
- Decide live vs historic migration based on case state, not call-site convenience
- Add a helper that dispatches to the right migration method automatically
- Monitor in-flight cases before starting historic migration batches
When it happens
Trigger: Calling migrateHistoricCaseInstance with the id of a case instance that is still running/active — its historic record exists (or its state is non-terminal) but it has not reached an end state.
Common situations: Migrating active cases with the historic API by mistake; a case that appears in history queries but is still in progress; copy-paste between the two migration code paths.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- task does not have an endTime, cannot delete
- Cannot find the historic case instance to migrate, with id${
- Task ${taskId} is already deleted
- historic case instanceIds are null
- historic case instanceIds are empty
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/6e4825e8307a2e2d.
Report an issue: GitHub.