flowable/flowable-engine · error · PropertyNotWritableException

resolver is read-only

Error message

resolver is read-only

What it means

Write protection in ListELResolver.setValue: a setValue operation was attempted on a list-backed EL resolver that was created read-only. The EL expression being evaluated tried to assign to a list element, but this resolver instance does not permit mutation.

Solutions

  1. Construct ListELResolver with readOnly=false
  2. Mutate the list outside EL instead of through the read-only resolver

Example fix

// before
new ListELResolver(true);
// after
new ListELResolver(false);
Defensive patterns

Strategy: type-guard

Validate before calling

boolean canWrite = !elResolverIsReadOnly; // construct resolver with readOnly=false if writes needed

Type guard

boolean writable(Object base) { return base instanceof java.util.List && resolverWritable; }

Try / catch

try {
  resolver.setValue(ctx, list, index, value);
} catch (PropertyNotWritableException e) {
  list.set(index, value); // mutate outside EL
}

Prevention

When it happens

Trigger: EL expression writing into a List (e.g. ${myList[0] = x}) while the resolver instance is read-only; the write path in setValue hits the readOnly flag after isResolvable(base).

Common situations: Immutable EL environments (e.g. read-only evaluation contexts in Flowable expression evaluation); constructing ListELResolver(true) by default and then attempting assignments in expressions.

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/f881da679f134220. Report an issue: GitHub.

Appendix: source

Thrown at modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/javax/el/ListELResolver.java:86

			if (idx < 0 || idx >= list.size()) {
				return null;
			}
			return list.get(idx);
		}

		return null;
	}

	@Override
	public void setValue(ELContext context, Object base, Object property, Object value) {
		Objects.requireNonNull(context, "context is null");

		if (isResolvable(base)) {
			context.setPropertyResolved(base, property);
			@SuppressWarnings("unchecked") // Must be OK to cast to Object
			List<Object> list = (List<Object>) base;
			if (readOnly) {
				throw new PropertyNotWritableException("resolver is read-only");
			}

			int idx = coerce(property);
			try {
				list.set(idx, value);
			} catch (UnsupportedOperationException e) {
				throw new PropertyNotWritableException(e);
			} catch (IndexOutOfBoundsException e) {
				throw new PropertyNotFoundException(e);
			}
		}
	}

	@Override
	public boolean isReadOnly(ELContext context, Object base, Object property) {
		Objects.requireNonNull(context, "context is null");

		if (isResolvable(base)) {

View on GitHub (pinned to d6d39ce1c6)