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

  1. Unwrap the Optional before the write: write to the contained value's holder, not the Optional itself
  2. 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)
  3. Replace Optional-returning accessors used in writable EL paths with plain getters
  4. 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

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)