flowable/flowable-engine · error · ActivitiException
form property ' ' is not writable
Error message
form property '${id}' is not writable What it means
Thrown by FormPropertyHandler.submitFormProperty when a form property is submitted for an execution but the property is marked as not writable (isWritable false, i.e. activiti:writable="false" or a read-only property) and the submitted map contains that id. FormPropertySession calls this for each submitted property when a form is submitted for a task/execution.
Solutions
- Remove read-only property ids from the submitted map before calling submitTaskFormData.
- Filter the payload using FormData.getFormProperties(): submit only entries whose FormProperty.isWritable() is true.
- Keep computed/read-only values out of client payloads entirely; let the engine fill them via their expression.
- If the property genuinely needs to be submitted, set activiti:writable="true" in the BPMN form property declaration.
Example fix
// before
formService.submitTaskFormData(taskId, allRenderedProps); // includes read-only ids
// after
Map<String, String> writable = new HashMap<>();
for (FormProperty fp : formService.getTaskFormData(taskId).getFormProperties()) {
if (fp.isWritable() && allRenderedProps.containsKey(fp.getId())) {
writable.put(fp.getId(), allRenderedProps.get(fp.getId()));
}
}
formService.submitTaskFormData(taskId, writable); Defensive patterns
Strategy: validation
Validate before calling
FormData data = formService.getTaskFormData(taskId);
Map<String,String> writable = new HashMap<>();
for (FormProperty fp : data.getFormProperties()) {
if (fp.isWritable() && submitted.containsKey(fp.getId())) {
writable.put(fp.getId(), submitted.get(fp.getId()));
}
}
formService.submitTaskFormData(taskId, writable); Try / catch
try {
formService.submitTaskFormData(taskId, props);
} catch (org.activiti.engine.ActivitiException e) {
if (e.getMessage().endsWith("is not writable")) {
LOGGER.warn("Stripping read-only props and resubmitting");
props.keySet().removeIf(id -> !isWritable(id));
formService.submitTaskFormData(taskId, props);
}
} Prevention
- Filter payloads by FormProperty.isWritable() before submission.
- Don't echo read-only/computed properties back from front-ends.
- Keep BPMN writable flags and client behavior in sync.
- Cover form submission in integration tests for each form property.
When it happens
Trigger: Calling formService.submitTaskFormData(taskId, properties) or saveTaskForm with a map containing the id of a read-only form property (e.g. one filled by the engine via expression, or declared writable=false in BPMN).
Common situations: Front-ends echoing back ALL rendered form properties (including read-only ones) in the submit payload; form definitions where a property was flipped to writable=false but the client wasn't updated; auto-generated forms posting back computed fields.
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 '" + id + "' is not writable
- form property ' ' is required
- event is null
- 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/6a8fd75d71286185.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable5-engine/src/main/java/org/activiti/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 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) {View on GitHub (pinned to d6d39ce1c6)