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

  1. Use a JsonNodeELResolver constructed with readOnly=false for the write path
  2. Rewrite the expression to compute a value instead of assigning to the JSON variable
  3. 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

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


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)