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

  1. 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
  2. Set a form key on the task/CMMN case model so the historic task records one
  3. If formKey exists but form info is needed, call taskService.getTaskFormModel yourself only after confirming a non-empty formKey
  4. 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

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


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)