{"record":{"id":"6a8fd75d71286185","repo":"flowable/flowable-engine","slug":"form-property-id-is-not-writable-6a8fd7","errorCode":null,"errorMessage":"form property '${id}' is not writable","messagePattern":"form property '(.+?)' is not writable","errorType":"validation","errorClass":"ActivitiException","httpStatus":null,"severity":"error","filePath":"modules/flowable5-engine/src/main/java/org/activiti/engine/impl/form/FormPropertyHandler.java","lineNumber":80,"sourceCode":"                modelValue = defaultExpression.getValue(NoExecutionVariableScope.getSharedInstance());\n            }\n        }\n\n        if (modelValue instanceof String) {\n            formProperty.setValue((String) modelValue);\n        } else if (type != null) {\n            String formValue = type.convertModelValueToFormValue(modelValue);\n            formProperty.setValue(formValue);\n        } else if (modelValue != null) {\n            formProperty.setValue(modelValue.toString());\n        }\n\n        return formProperty;\n    }\n\n    public void submitFormProperty(ExecutionEntity execution, Map<String, String> properties) {\n        if (!isWritable && properties.containsKey(id)) {\n            throw new ActivitiException(\"form property '\" + id + \"' is not writable\");\n        }\n\n        if (isRequired && !properties.containsKey(id) && defaultExpression == null) {\n            throw new ActivitiException(\"form property '\" + id + \"' is required\");\n        }\n        boolean propertyExists = false;\n        Object modelValue = null;\n        if (properties.containsKey(id)) {\n            propertyExists = true;\n            final String propertyValue = properties.remove(id);\n            if (type != null) {\n                modelValue = type.convertFormValueToModelValue(propertyValue);\n            } else {\n                modelValue = propertyValue;\n            }\n        } else if (defaultExpression != null) {\n            final Object expressionValue = defaultExpression.getValue(execution);\n            if (type != null && expressionValue != null) {","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable5-engine/src/main/java/org/activiti/engine/impl/form/FormPropertyHandler.java#L62-L98","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nformService.submitTaskFormData(taskId, allRenderedProps); // includes read-only ids\n// after\nMap<String, String> writable = new HashMap<>();\nfor (FormProperty fp : formService.getTaskFormData(taskId).getFormProperties()) {\n    if (fp.isWritable() && allRenderedProps.containsKey(fp.getId())) {\n        writable.put(fp.getId(), allRenderedProps.get(fp.getId()));\n    }\n}\nformService.submitTaskFormData(taskId, writable);","handlingStrategy":"validation","validationCode":"FormData data = formService.getTaskFormData(taskId);\nMap<String,String> writable = new HashMap<>();\nfor (FormProperty fp : data.getFormProperties()) {\n    if (fp.isWritable() && submitted.containsKey(fp.getId())) {\n        writable.put(fp.getId(), submitted.get(fp.getId()));\n    }\n}\nformService.submitTaskFormData(taskId, writable);","typeGuard":null,"tryCatchPattern":"try {\n    formService.submitTaskFormData(taskId, props);\n} catch (org.activiti.engine.ActivitiException e) {\n    if (e.getMessage().endsWith(\"is not writable\")) {\n        LOGGER.warn(\"Stripping read-only props and resubmitting\");\n        props.keySet().removeIf(id -> !isWritable(id));\n        formService.submitTaskFormData(taskId, props);\n    }\n}","preventionTips":["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."],"tags":["forms","read-only","validation","workflow"],"backgroundTag":"invalid-argument-value","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-18T11:17:12.947Z"}