flowable/flowable-engine · error · FlowableException
form property '" + id + "' is not writable
Error message
form property '" + id + "' is not writable
What it means
FormPropertyHandler.submitFormProperty enforces the writability flag of each BPMN form property (flowable:writable="false"). If a submitted property map contains a value for a read-only property, FlowableException "form property '<id>' is not writable" is thrown, preventing clients from injecting values into properties marked read-only.
Solutions
- Remove the read-only key from the submitted map before calling the form service.
- If the field must be editable, set flowable:writable="true" (or omit it) on the form property in the BPMN.
- Filter submissions by querying getTaskFormData(...).getFormProperties() and only submitting writable ones.
- Update the client UI to disable submission of non-writable fields.
Example fix
// before
formService.submitTaskFormData(taskId, allRenderedFields); // includes readOnly field 'status'
// after
Map<String,String> writable = new HashMap<>();
for (FormProperty p : formService.getTaskFormData(taskId).getFormProperties()) {
if (p.isWritable() && allRenderedFields.containsKey(p.getId())) {
writable.put(p.getId(), allRenderedFields.get(p.getId()));
}
}
formService.submitTaskFormData(taskId, writable); Defensive patterns
Strategy: validation
Validate before calling
Map<String,String> submit = properties.entrySet().stream()
.filter(e -> formProperties.stream()
.anyMatch(p -> p.getId().equals(e.getKey()) && p.isWritable()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); Try / catch
try {
formService.submitTaskFormData(taskId, properties);
} catch (FlowableException e) {
if (e.getMessage().endsWith("is not writable")) {
String readOnlyId = extractPropertyId(e.getMessage());
properties.remove(readOnlyId);
formService.submitTaskFormData(taskId, properties);
} else { throw e; }
} Prevention
- Build submission maps only from FormProperty.isWritable()==true entries.
- Configure UI fields with writable=false as display-only (no input binding).
- Review BPMN form property attributes when forms change.
- Never reuse a shared 'all fields' map across different form submissions.
When it happens
Trigger: FormService.submitTaskFormData / startProcessInstanceByForm with a map containing a key whose FormProperty was declared writable="false" in the BPMN start/task form.
Common situations: Generic form UIs that submit every rendered field including read-only ones, clients copying start-form values into task-form submissions, accidental reuse of a shared properties map across forms.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- form property ' ' is not writable
- form property '" + id + "' is required
- invalid date value " + propertyValue
- Invalid value for enum form property: + value
- A channel key detection value is required for the channel…
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/4864c34488b027d0.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-engine/src/main/java/org/flowable/engine/impl/form/FormPropertyHandler.java:80
modelValue = defaultExpression.getValue(NoExecutionVariableScope.getSharedInstance());
}
}
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) {View on GitHub (pinned to d6d39ce1c6)