flowable/flowable-engine · error · FlowableException

form property '" + id + "' is required

Error message

form property '" + id + "' is required

What it means

FormPropertyHandler.submitFormProperty enforces the required flag of a BPMN form property (flowable:required="true"). If the submitted map lacks the property and no default expression is defined, FlowableException "form property '<id>' is required" is thrown. Default expressions satisfy the requirement without an explicit value.

Solutions

  1. Include a value for the required property in the submitted map.
  2. Add flowable:default="${...}" to the form property in the BPMN if a sensible default exists.
  3. Set flowable:required="false" if the field is genuinely optional.
  4. Validate the required set client-side: compare submitted keys with form properties where isRequired() before submission.

Example fix

// before
formService.submitTaskFormData(taskId, Map.of("comment", "ok")); // 'approval' is required
// after
Map<String,String> props = new HashMap<>(Map.of("comment", "ok"));
props.put("approval", "approved");
formService.submitTaskFormData(taskId, props);
Defensive patterns

Strategy: validation

Validate before calling

List<String> missing = formService.getTaskFormData(taskId).getFormProperties().stream()
    .filter(FormProperty::isRequired)
    .map(FormProperty::getId)
    .filter(id -> !properties.containsKey(id))
    .collect(Collectors.toList());
if (!missing.isEmpty()) throw new IllegalArgumentException("Missing required form properties: " + missing);

Try / catch

try {
    formService.submitTaskFormData(taskId, properties);
} catch (FlowableException e) {
    if (e.getMessage().endsWith("is required")) {
        String missingId = extractPropertyId(e.getMessage());
        properties.put(missingId, resolveDefaultValue(missingId));
        formService.submitTaskFormData(taskId, properties);
    } else { throw e; }
}

Prevention

When it happens

Trigger: FormService.submitTaskFormData / startProcessInstanceByForm where the properties map omits a form property declared required="true" and whose flowable:default expression is absent.

Common situations: UIs with optional-but-required fields, clients submitting partial maps, BPMN updated to require a new field while existing callers don't send it, conditional fields made required incorrectly.

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/dcd45933c5094b00. Report an issue: GitHub.

Appendix: source

Thrown at modules/flowable-engine/src/main/java/org/flowable/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 FlowableException("form property '" + id + "' is not writable");
        }

        if (isRequired && !properties.containsKey(id) && defaultExpression == null) {
            throw new FlowableException("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)