flowable/flowable-engine · error · ELException

Expected LambdaExpression but got: " + (lambdaObj == null ?

Error message

Expected LambdaExpression but got: " + (lambdaObj == null ? "null" : lambdaObj.getClass().getName())

What it means

AstLambdaInvocation.eval evaluates the expression being invoked and requires it to be a LambdaExpression instance. If the evaluated target is null or any other type, it throws ELException describing the actual type. This means the code invoked something as a lambda function that did not evaluate to a lambda.

Source

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

    public AstLambdaInvocation(AstNode lambdaNode, AstParameters params) {
        this.lambdaNode = lambdaNode;
        this.params = params;
    }

    @Override
    public void appendStructure(StringBuilder builder, Bindings bindings) {
        lambdaNode.appendStructure(builder, bindings);
        params.appendStructure(builder, bindings);
    }

    @Override
    public Object eval(Bindings bindings, ELContext context) {
        // Evaluate the lambda expression
        Object lambdaObj = lambdaNode.eval(bindings, context);

        if (!(lambdaObj instanceof LambdaExpression)) {
            throw new ELException("Expected LambdaExpression but got: " +
                (lambdaObj == null ? "null" : lambdaObj.getClass().getName()));
        }

        LambdaExpression lambda = (LambdaExpression) lambdaObj;

        // Evaluate the arguments
        Object[] args = params.eval(bindings, context);

        // Invoke the lambda
        Object result = lambda.invoke(context, args);
        return result;
    }

    @Override
    public int getCardinality() {
        return 2;
    }

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Verify the invoked identifier is defined as a lambda, e.g. define it via the ELContext VariableMapper or a surrounding lambda expression.
  2. Null-check the variable before invocation: ${not empty f ? f(x) : ''}.
  3. Correct typos/scoping so the lambda variable is visible at the invocation site.
  4. Log/bind the value to confirm its runtime type before invoking it.

Example fix

// before
${myFuntion(42)}  // typo, resolves to null
// after
${myFunction(42)} // where myFunction := x -> x + 1
Defensive patterns

Strategy: type-guard

Validate before calling

Object f = factory.createValueExpression(ctx, "${fn}", Object.class).getValue(context);
if (f == null) throw new IllegalStateException("Lambda variable 'fn' is not bound");

Type guard

boolean isLambda(Object v) { return v instanceof LambdaExpression; }

Try / catch

try {
    return lambdaInvocationExpr.getValue(context);
} catch (ELException e) {
    if (!e.getMessage().startsWith("Expected LambdaExpression")) throw e;
    return null; // or fallback invocation path
}

Prevention

When it happens

Trigger: Writing ${someIdentifier(args)} where someIdentifier evaluates to null or a non-lambda value (String, number, bean) instead of a lambda defined like (x -> x*2)(5); referencing a lambda variable before it is bound.

Common situations: Typos in lambda variable names; lambda defined in a different scope than the invocation; passing a non-lambda value into a position the caller treats as a function; null process variables used as functions.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/597d1c5b76304551. Report an issue: GitHub.