flowable/flowable-engine · error · PropertyNotWritableException
resolver is read-only
Error message
resolver is read-only
What it means
BeanELResolver.setValue throws PropertyNotWritableException("resolver is read-only") when a value is assigned through an EL expression on a resolver that was constructed with readOnly=true. The resolver refuses all writes regardless of the target bean.
Source
Thrown at modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/javax/el/BeanELResolver.java:172
return invoke(m, base, (Object[]) null);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
Util.handleThrowable(cause);
throw new ELException("Error reading '" + property + "' on type '" + base.getClass().getName() + "'", cause);
} catch (Exception e) {
throw new ELException(e);
}
}
@Override
public void setValue(ELContext context, Object base, Object property, Object value) {
Objects.requireNonNull(context, "context is null");
if (base == null || property == null) {
return;
}
if (readOnly) {
throw new PropertyNotWritableException("resolver is read-only");
}
BeanProperty beanProperty = property(base, property);
if (beanProperty == null) {
return;
}
Method method = beanProperty.write(base);
if (method == null) {
throw new PropertyNotWritableException("Cannot write property: '" + property + "' on type " + base.getClass().getName());
}
context.setPropertyResolved(base, property);
try {
invoke(method, base, value);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
Util.handleThrowable(cause);
throw new ELException("Error '" + property + "' on type '" + base.getClass().getName() + "'", cause);View on GitHub (pinned to d6d39ce1c6)
Solutions
- Remove the assignment from the expression; use reads only.
- If writes are legitimately needed, construct the resolver with readOnly=false.
- Perform the mutation in code (bean setter) rather than inside the EL expression.
Example fix
// before ELResolver r = new BeanELResolver(true); // read-only // after ELResolver r = new BeanELResolver(false); // only if writes are intended
Defensive patterns
Strategy: validation
Validate before calling
if (needsWrites) resolver = new BeanELResolver(false); // else keep readOnly=true and never emit assignment expressions
Try / catch
try { resolver.setValue(ctx, base, prop, val); } catch (PropertyNotWritableException e) { /* route write elsewhere or rebuild resolver writable */ } Prevention
- Keep assignment expressions out of read-only evaluation contexts
- Document which resolvers in the chain are read-only
- Mutate beans in code, not in EL expressions
When it happens
Trigger: Evaluating an assignment expression like ${bean.name = 'x'} (or calling setValue directly) against a BeanELResolver created as new BeanELResolver(true) — e.g. a shared read-only resolver used in Flowable's EL context.
Common situations: Reusing a read-only resolver configured for safe expression evaluation in a context that also attempts writes; accidental assignment syntax in templates meant to be read-only.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- error.value.set.rvalue
- Error reading '' on type ''
- ELException
- Cannot write property: '${property}' on type ${base.getClass
- Error '${property}' on type '${base.getClass().getName()}'
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/92baddd7a971ab53.
Report an issue: GitHub.