flowable/flowable-engine · error · ActivitiException
form property ' ' is required
Error message
form property '${id}' is required What it means
FormPropertyHandler throws this when a form property declared as required (activiti:required="true") is absent from the submitted properties map and no defaultExpression can supply a value. The engine refuses to complete the form submission so the process variable is never set from a missing value. It is a validation guard in submitFormProperty during form property submission.
Solutions
- Add the missing property key to the submitted properties map with a value
- Set a defaultExpression on the form property in the BPMN XML so a missing value is acceptable
- Remove the activiti:required attribute if the field is truly optional
- Fix key/typo mismatch between the UI field name and the form property id
Example fix
// before
formService.submitTaskFormData(taskId, Collections.singletonMap("comment", "ok")); // 'amount' is required
// after
Map<String, String> props = new HashMap<>();
props.put("comment", "ok");
props.put("amount", "100");
formService.submitTaskFormData(taskId, props); Defensive patterns
Strategy: validation
Validate before calling
Map<String,String> props = formService.getTaskFormModel(taskId) != null ? submitted : new HashMap<>();
for (FormInfo fi : formService.getTaskFormData(taskId).getFormProperties()) {
if (fi.isRequired() && !submitted.containsKey(fi.getId()) && fi.getDefaultExpression() == null)
throw new IllegalArgumentException("Missing required form property: " + fi.getId());
} Type guard
boolean isComplete(Map<String,FormProperty> meta, Map<String,String> submitted) {
return meta.values().stream()
.filter(FormProperty::isRequired)
.allMatch(p -> submitted.containsKey(p.getId()) || p.getDefaultExpression() != null);
} Try / catch
try {
formService.submitTaskFormData(taskId, props);
} catch (FlowableException e) {
if (e.getMessage() != null && e.getMessage().contains("is required")) {
// surface missing-field error to the user
} else throw e;
} Prevention
- Render the form from the engine's FormData metadata so required fields are always present
- Validate all required ids client-side before submission
- Re-check required flags after any BPMN model change
- Submit full property maps, not sparse deltas
When it happens
Trigger: Calling FormService.submitTaskFormData(taskId, properties) or submitFormProperty with a map that omits an id whose FormPropertyHandler has isRequired=true and defaultExpression==null.
Common situations: UI form omits a required field; client sends only changed fields; process model was changed to mark a property required after clients were built; key typo means containsKey(id) is false even though the user filled something.
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
- form property ' ' is not writable
- event is null
- form property '" + id + "' is not writable
- form property '" + id + "' is required
- invalid date value " + propertyValue
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/e86740cdc9184fab.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable5-engine/src/main/java/org/activiti/engine/impl/form/FormPropertyHandler.java:84
if (modelValue instanceof String) {
formProperty.setValue((String) modelValue);
} else if (type != null) {
String formValue = type.convertModelValueToFormValue(modelValue);
formProperty.setValue(formValue);
} else if (modelValue != null) {
formProperty.setValue(modelValue.toString());
}
return formProperty;
}
public void submitFormProperty(ExecutionEntity execution, Map<String, String> properties) {
if (!isWritable && properties.containsKey(id)) {
throw new ActivitiException("form property '" + id + "' is not writable");
}
if (isRequired && !properties.containsKey(id) && defaultExpression == null) {
throw new ActivitiException("form property '" + id + "' is required");
}
boolean propertyExists = false;
Object modelValue = null;
if (properties.containsKey(id)) {
propertyExists = true;
final String propertyValue = properties.remove(id);
if (type != null) {
modelValue = type.convertFormValueToModelValue(propertyValue);
} else {
modelValue = propertyValue;
}
} else if (defaultExpression != null) {
final Object expressionValue = defaultExpression.getValue(execution);
if (type != null && expressionValue != null) {
modelValue = type.convertFormValueToModelValue(expressionValue.toString());
} else if (expressionValue != null) {
modelValue = expressionValue.toString();
} else if (isRequired) {View on GitHub (pinned to d6d39ce1c6)