flowable/flowable-engine · error · PropertyNotWritableException
ELResolver not writable for type
Error message
ELResolver not writable for type '${base.getClass().getName()}' What it means
OptionalELResolver unwraps java.util.Optional values in EL, but Optionals are immutable value wrappers. setValue throws PropertyNotWritableException whenever the base is an Optional, because setting a property on an Optional has no meaningful semantics.
Solutions
- Unwrap the Optional before the write: write to the contained value's holder, not the Optional itself
- Refactor so the expression reads from Optional but writes go through a bean setter (e.g. ${bean.name = x} where bean is the unwrapped object)
- Replace Optional-returning accessors used in writable EL paths with plain getters
- Catch PropertyNotWritableException around the setValue call
Example fix
// before Optional<User> opt = service.findUser(); el.setValue(ctx, opt, "name", "Ada"); // throws // after User user = service.findUser().orElseThrow(); el.setValue(ctx, user, "name", "Ada");
Defensive patterns
Strategy: type-guard
Validate before calling
if (base instanceof Optional) throw new IllegalArgumentException("unwrap Optional before EL write"); Type guard
Object unwrap(Object o){ return o instanceof Optional<?> opt ? opt.orElse(null) : o; } Try / catch
try { expr.setValue(ctx, base, value); } catch (PropertyNotWritableException e) { /* base was an Optional; unwrap and retry on the contained value */ } Prevention
- Unwrap Optionals before passing to EL writes
- Keep Optional only for read paths
- Document that Optional-returning getters are read-only in EL
When it happens
Trigger: Calling ValueExpression.setValue (or an assignment expression) where the base resolved to an Optional object, e.g. ${opt.value = x} where opt is Optional<String>; any write targeting an Optional-returning accessor.
Common situations: A bean/service method was refactored to return Optional<T>, and existing expressions that assigned into its properties now target an Optional base; developers expect Optional to be transparent for writes as it is for reads.
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/6bed458f8c6e74e2.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/javax/el/OptionalELResolver.java:104
if (base instanceof Optional) {
context.setPropertyResolved(base, property);
}
return null;
}
/**
* {@inheritDoc}
* <p>
* If the base object is an {@link Optional} this method always throws a {@link PropertyNotWritableException} since
* instances of this resolver are always read-only.
*/
@Override
public void setValue(ELContext context, Object base, Object property, Object value) {
Objects.requireNonNull(context);
if (base instanceof Optional) {
throw new PropertyNotWritableException("ELResolver not writable for type '" + base.getClass().getName() + "'");
}
}
/**
* {@inheritDoc}
*
* @return If the base object is an {@link Optional} this method always returns {@code true} since instances of this
* resolver are always read-only.
* <p>
* If the base object is not an {@link Optional} then the return value is undefined.
*/
@Override
public boolean isReadOnly(ELContext context, Object base, Object property) {
Objects.requireNonNull(context);
if (base instanceof Optional) {
context.setPropertyResolved(base, property);
return true;View on GitHub (pinned to d6d39ce1c6)