flowable/flowable-engine · error · PropertyNotWritableException
resolver is read-only
Error message
resolver is read-only
What it means
The object-node branch of JsonNodeELResolver.setValue throws PropertyNotWritableException('resolver is read-only') when the resolver was constructed with readOnly=true and an object property write is attempted. Read-only resolvers are used in Flowable to protect expression evaluation from mutating underlying data.
Solutions
- Use a JsonNodeELResolver constructed with readOnly=false for the write path
- Rewrite the expression to compute a value instead of assigning to the JSON variable
- Perform the mutation via Flowable variable-service APIs (setVariable / JsonUtil) instead of EL assignment
Example fix
// before JsonNodeELResolver resolver = new JsonNodeELResolver(true); // read-only expression.setValue(value, ctx); // PropertyNotWritableException // after JsonNodeELResolver resolver = new JsonNodeELResolver(false); expression.setValue(value, ctx);
Defensive patterns
Strategy: validation
Validate before calling
if (expressionContainsAssignment(exprText) && resolverReadOnly) { throw new IllegalStateException("Cannot assign to JSON properties with a read-only EL resolver: " + exprText); } Try / catch
try { expr.setValue(value, ctx); } catch (PropertyNotWritableException e) { // fall back to variable-service mutation
execution.setVariable(varName, updatedJson); } Prevention
- Know which Flowable evaluation contexts are read-only and never write from expressions there
- Mutate JSON variables via execution.setVariable/JsonUtil instead of EL assignment
- Search process definitions for '=' assignment expressions when configuring read-only resolvers
When it happens
Trigger: Evaluating an expression that assigns a value to a JSON object property (e.g. ${jsonVar.field = x}) while the EL resolver was created read-only (the default in several Flowable evaluation paths).
Common situations: Using an expression that mutates a JSON variable inside a read-only evaluation context (e.g. condition/listener expressions in BPMN); calling ValueExpression.setValue on a read-only resolver configuration.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- Cannot change fixed value with
- Cannot coerce property to array index:
- Cannot parse array index:
- Cannot set value of '', it resolves to a bean defined in…
- Cannot write property
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/e10e275ae0817acc.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/el/JsonNodeELResolver.java:290
*/
@Override
public void setValue(ELContext context, Object base, Object property, Object value) {
if (context == null) {
throw new NullPointerException("context is null");
}
if (isResolvable(base)) {
FlowableJsonNode baseNode = JsonUtil.asFlowableJsonNode(base);
if (baseNode instanceof FlowableObjectNode objectNode) {
setValue(context, objectNode, property, value);
} else if (baseNode instanceof FlowableArrayNode arrayNode) {
setValue(context, arrayNode, property, value);
}
}
}
protected void setValue(ELContext context, FlowableObjectNode node, Object property, Object value) {
if (readOnly) {
throw new PropertyNotWritableException("resolver is read-only");
}
String propertyName = property.toString();
if (value instanceof BigDecimal bigDecimal) {
node.put(propertyName, bigDecimal);
} else if (value instanceof Boolean booleanValue) {
node.put(propertyName, booleanValue);
} else if (value instanceof Integer integerValue) {
node.put(propertyName, integerValue);
} else if (value instanceof Long longValue) {
node.put(propertyName, longValue);
} else if (value instanceof Double doubleValue) {
node.put(propertyName, doubleValue);
} else if (JsonUtil.isJsonNode(value)) {View on GitHub (pinned to d6d39ce1c6)