flowable/flowable-engine · error · ELException

Lambda body is not an lvalue

Error message

Lambda body is not an lvalue

What it means

The AST node representing a lambda expression's body does not support assignment. When setValue is called on the lambda body (e.g. via ValueExpression.setValue on an expression containing a lambda), the library throws ELException('Lambda body is not an lvalue') because a lambda result is a transient value, not a writable target.

Source

Thrown at modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/de/odysseus/el/tree/impl/ast/AstLambdaExpression.java:88

     * Simple ValueExpression implementation that wraps an AstNode for lambda body evaluation.
     */
    private static class LambdaBodyValueExpression extends ValueExpression {
        private final Bindings bindings;
        private final AstNode body;

        public LambdaBodyValueExpression(Bindings bindings, AstNode body) {
            this.bindings = bindings;
            this.body = body;
        }

        @Override
        public Object getValue(ELContext context) throws ELException {
            return body.eval(bindings, context);
        }

        @Override
        public void setValue(ELContext context, Object value) throws ELException {
            throw new ELException("Lambda body is not an lvalue");
        }

        @Override
        public boolean isReadOnly(ELContext context) throws ELException {
            return true;
        }

        @Override
        public Class<?> getType(ELContext context) throws ELException {
            return Object.class;
        }

        @Override
        public Class<?> getExpectedType() {
            return Object.class;
        }

        @Override

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Do not call setValue on lambda expressions; restructure so assignment targets a variable or bean property, e.g. ${myVar} not a lambda.
  2. If you intended to update the variable captured by the lambda, set that variable directly via the ELContext VariableMapper or resolver instead.
  3. Guard caller code with isReadOnly() before calling setValue and skip/skip-log read-only expressions.

Example fix

// before
exprNode.setValue(context, newValue); // expr is a lambda
// after
// set the underlying variable instead
context.getVariableMapper().setVariable("myVar", valueExpr);
Defensive patterns

Strategy: validation

Validate before calling

if (valueExpression.isReadOnly(context)) {
    throw new UnsupportedOperationException("Expression is read-only (lambda): " + valueExpression.getExpressionString());
}

Type guard

boolean isWritableTarget(ValueExpression ve, ELContext ctx) {
    return ve != null && !ve.isLiteralText() && !ve.isReadOnly(ctx);
}

Try / catch

try {
    ve.setValue(context, newValue);
} catch (ELException e) {
    if (!e.getMessage().contains("not an lvalue")) throw e;
    // route assignment to a variable/property instead
}

Prevention

When it happens

Trigger: Calling ValueExpression.setValue(bindings, context, value) on an expression whose root is a lambda expression, e.g. assigning to ${x -> x + 1} or a lambda invocation result; using EL resolver set operations that walk into an AstLambdaExpression body.

Common situations: Misusing setValue on a computed expression instead of a variable; framework code (e.g. UI binding or process variable update) generically calls setValue on parsed expressions that happen to contain lambdas.

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