flowable/flowable-engine · warning · FlowableIllegalArgumentException
Task has no form defined
Error message
Task has no form defined
What it means
FlowableIllegalArgumentException thrown by the historic task form endpoint when the historic task instance exists but its formKey is empty. The endpoint cannot return a form model for a task that was never associated with a form, so it rejects the request early. This typically means the task's form definition was not set (or not captured in history) when the task was created/completed.
Solutions
- Verify the task actually has a form key via GET /cmmn-history/historic-task-instances/{taskId} and checking the formKey field before calling the form endpoint
- Set a form key on the task/CMMN case model so the historic task records one
- If formKey exists but form info is needed, call taskService.getTaskFormModel yourself only after confirming a non-empty formKey
- Check Flowable version for changes in form-key capture on historic tasks; upgrade/patch if form keys were previously stored
Example fix
// before
String form = client.getFormForHistoricTask(taskId); // 500/400 when no form
// after
HistoricTaskInstance task = client.getHistoricTask(taskId);
String form = (task.getFormKey() != null && !task.getFormKey().isEmpty())
? client.getFormForHistoricTask(taskId) : null; Defensive patterns
Strategy: validation
Validate before calling
const task = await fetch(`/cmmn-history/historic-task-instances/${taskId}`).then(r => r.json());
if (!task.formKey) return null; // no form defined, skip form fetch Type guard
function hasForm(task) {
return task != null && typeof task.formKey === 'string' && task.formKey.length > 0;
} Try / catch
try {
return await fetchForm(taskId);
} catch (e) {
if (String(e.message).includes('Task has no form defined')) return null;
throw e;
} Prevention
- Check formKey on the historic task resource before requesting the form
- Ensure tasks are created with a form key when forms are expected
- Test form endpoints against tasks created programmatically without forms
- Account for form-key persistence differences when upgrading Flowable versions
When it happens
Trigger: GET /cmmn-history/historic-task-instances/{taskId}/form for a task whose historic FormKey is null/empty — e.g. the task was created without a form, or formKey handling changed between Flowable versions so form keys are no longer stored on historic tasks.
Common situations: Tasks created programmatically (TaskService) with no form key; CMMN tasks whose form key was never set in the model; querying form for very old historic tasks after a Flowable upgrade changed form-key persistence; formProvider/flowable-form-engine not deployed so form keys were never resolved.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- Not both a taskId and a processDefinitionId parameter can…
- The taskId or processDefinitionId parameter has to be…
- A group or a user is required to create an identity link.
- A group or a user is required to create an identity link.
- Authentication failed for this username and password
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/8b8b7f1530eececb.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/history/task/HistoricTaskInstanceResource.java:94
HistoricTaskInstance task = getHistoricTaskInstanceFromRequestWithoutAccessCheck(taskId);
if (restApiInterceptor != null) {
restApiInterceptor.deleteHistoricTask(task);
}
historyService.deleteHistoricTaskInstance(taskId);
}
@ApiOperation(value = "Get a historic task instance form", tags = { "History Task" })
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Indicates request was successful and the task form is returned"),
@ApiResponse(code = 404, message = "Indicates the requested task was not found.")
})
@GetMapping(value = "/cmmn-history/historic-task-instances/{taskId}/form", produces = "application/json")
public String getTaskForm(@ApiParam(name = "taskId") @PathVariable String taskId) {
HistoricTaskInstance task = getHistoricTaskInstanceFromRequest(taskId);
if (StringUtils.isEmpty(task.getFormKey())) {
throw new FlowableIllegalArgumentException("Task has no form defined");
}
FormInfo formInfo = taskService.getTaskFormModel(task.getId());
if (formHandlerRestApiInterceptor != null) {
return formHandlerRestApiInterceptor.convertHistoricTaskFormInfo(formInfo, task);
} else {
SimpleFormModel formModel = (SimpleFormModel) formInfo.getFormModel();
return restResponseFactory.getFormModelString(new FormModelResponse(formInfo, formModel));
}
}
}
View on GitHub (pinned to d6d39ce1c6)