flowable/flowable-engine · error · FlowableIllegalArgumentException
formInfo is null
Error message
formInfo is null
What it means
ProcessInstanceBuilderImpl.formVariables registers extra form-related variables for a process start. It requires a non-null FormInfo; passing null throws FlowableIllegalArgumentException because form variable resolution (fields, outcome) cannot proceed without it.
Source
Thrown at modules/flowable-engine/src/main/java/org/flowable/engine/impl/runtime/ProcessInstanceBuilderImpl.java:240
@Override
public ProcessInstanceBuilder startFormVariable(String variableName, Object value) {
if (this.startFormVariables == null) {
this.startFormVariables = new HashMap<>();
}
this.startFormVariables.put(variableName, value);
return this;
}
@Override
public ProcessInstanceBuilder outcome(String outcome) {
this.outcome = outcome;
return this;
}
@Override
public ProcessInstanceBuilder formVariables(Map<String, Object> formVariables, FormInfo formInfo, String formOutcome) {
if (formInfo == null) {
throw new FlowableIllegalArgumentException("formInfo is null");
}
if (this.extraFormVariables == null) {
this.extraFormVariables = new HashMap<>();
}
if (formVariables != null) {
this.extraFormVariables.putAll(formVariables);
}
this.extraFormInfo = formInfo;
this.extraFormOutcome = formOutcome;
return this;
}
@Override
public ProcessInstanceBuilder fallbackToDefaultTenant() {View on GitHub (pinned to d6d39ce1c6)
Solutions
- Only call formVariables when a FormInfo was actually resolved; guard with if (formInfo != null).
- If you have plain variables with no form, use processInstanceBuilder.variables(map) instead of formVariables.
- Fix the form lookup so it returns a valid FormInfo (check the form definition exists for the process).
- Skip form-outcome handling when there is no form.
Example fix
// before
builder.formVariables(vars, formRepositoryService.getFormInfo(...), outcome); // may pass null
// after
FormInfo formInfo = formRepositoryService.getFormInfo(...);
if (formInfo != null) {
builder.formVariables(vars, formInfo, outcome);
} else {
builder.variables(vars);
} Defensive patterns
Strategy: type-guard
Validate before calling
if (formInfo == null) {
builder.variables(vars); // plain path
} else {
builder.formVariables(vars, formInfo, formOutcome);
} Type guard
boolean hasForm(FormInfo fi) { return fi != null && fi.getFormKey() != null; } Try / catch
try {
builder.formVariables(vars, formInfo, outcome);
} catch (FlowableIllegalArgumentException e) {
log.error("formVariables rejected: {}", e.getMessage());
throw e;
} Prevention
- Resolve FormInfo via FormRepositoryService and null-check before use.
- Fall back to plain variables() when no start form exists.
- Handle processes without start forms explicitly in start-form UI flows.
When it happens
Trigger: Calling processInstanceBuilder.formVariables(map, null, outcome); passing a FormInfo that a lookup (form repository service) returned as null because the form key/model was not found; wiring form metadata conditionally and passing null when absent.
Common situations: Start-form integration where the process has no start form defined, so the retrieved FormInfo is null; refactored code that dropped the form lookup; version changes in the form model API (task/process form handling).
Related errors
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/a269a1d5962df17f.
Report an issue: GitHub.