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;
}
@OverrideView on GitHub (pinned to d6d39ce1c6)
Solutions
- Do not call setValue on lambda expressions; restructure so assignment targets a variable or bean property, e.g. ${myVar} not a lambda.
- If you intended to update the variable captured by the lambda, set that variable directly via the ELContext VariableMapper or resolver instead.
- 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
- Only call setValue on property expressions, never on lambdas or method calls
- Check isReadOnly() before setValue
- Assign captured variables via the VariableMapper, not the lambda body
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
- error.coerce.type
- ' + token.getImage() + '
- error.identifier.method.notamethod
- Expected LambdaExpression but got: " + (lambdaObj == null ?
- error.value.set.rvalue
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/cdcfe6ffde73137d.
Report an issue: GitHub.