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

  1. Add the missing property key to the submitted properties map with a value
  2. Set a defaultExpression on the form property in the BPMN XML so a missing value is acceptable
  3. Remove the activiti:required attribute if the field is truly optional
  4. 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

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


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)