flowable/flowable-engine · error · FlowableIllegalArgumentException

Custom properties resolver expression " + expression + " did

Error message

Custom properties resolver expression " + expression + " did not return a Map<String, Object>

What it means

ExpressionCustomPropertiesResolver evaluates a Flowable expression and expects it to resolve to a Map<String, Object> of custom properties. When the expression returns anything else (String, null, List, etc.), getCustomPropertiesMap throws FlowableIllegalArgumentException because the resolved value cannot be used as a custom properties map.

Source

Thrown at modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/listener/ExpressionCustomPropertiesResolver.java:42

 * An {@link CustomPropertiesResolver} that evaluates a {@link Expression} when notified.
 * 
 * @author Yvo Swillens
 */
public class ExpressionCustomPropertiesResolver implements CustomPropertiesResolver {

    protected Expression expression;

    public ExpressionCustomPropertiesResolver(Expression expression) {
        this.expression = expression;
    }

    @Override
    public Map<String, Object> getCustomPropertiesMap(DelegateExecution execution) {
        Object expressionValue = expression.getValue(execution);
        if (expressionValue instanceof Map) {
            return (Map<String, Object>) expressionValue;
        } else {
            throw new FlowableIllegalArgumentException("Custom properties resolver expression " + expression + " did not return a Map<String, Object>");
        }
    }

    /**
     * returns the expression text for this execution listener. Comes in handy if you want to check which listeners you already have.
     */
    public String getExpressionText() {
        return expression.getExpressionText();
    }
}

View on GitHub (pinned to d6d39ce1c6)

Solutions

  1. Change the expression target so it returns a java.util.Map<String, Object> (e.g. bean method returning Map<String, Object>)
  2. If the bean returns a POJO, add a wrapper method that converts it to a Map before returning
  3. Verify the expression resolves (log or unit-test expression.getValue(execution)) — null usually means a typo or missing bean

Example fix

// before
public String getProperties() { return "a=1"; }
// after
public Map<String, Object> getProperties() {
    Map<String, Object> props = new HashMap<>();
    props.put("a", 1);
    return props;
}
Defensive patterns

Strategy: validation

Validate before calling

Object value = ((ExpressionCustomPropertiesResolver) resolver).getExpression().getValue(execution);
if (!(value instanceof Map)) {
    throw new IllegalStateException("Resolver expression must return Map<String, Object>, got: " + (value == null ? "null" : value.getClass().getName()));
}

Type guard

boolean isValidResolver(Object v) { return v instanceof Map<?, ?>; }

Try / catch

try {
    Map<String, Object> props = resolver.getCustomPropertiesMap(execution);
} catch (FlowableIllegalArgumentException e) {
    log.warn("Custom properties resolver returned non-Map: {}", e.getMessage());
}

Prevention

When it happens

Trigger: A custom properties resolver is configured in BPMN as an expression (e.g. flowable:customPropertiesResolverExpression="${someBean}") and the referenced bean/method returns a non-Map value, or the expression fails to resolve and returns null.

Common situations: Developer points the customPropertiesResolver expression at a bean method returning a List or POJO instead of Map<String, Object>; typo in expression causing null return; copy-pasted execution-listener expression that returns a String.

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