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
- Change the expression target so it returns a java.util.Map<String, Object> (e.g. bean method returning Map<String, Object>)
- If the bean returns a POJO, add a wrapper method that converts it to a Map before returning
- 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
- Type resolver bean methods as Map<String, Object> explicitly
- Unit-test expressions with a mock DelegateExecution before deploying
- Avoid reusing execution-listener expressions for property resolvers
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
- completionCondition '${activeCompletionCondition}' does not
- Skip expression does not resolve to a boolean: " + skipExpre
- ${collectionExpressionText}' didn't resolve to a Collection
- Category expression does not resolve to a string: %s
- Skip expression does not resolve to a boolean: ${skipExpress
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/9db51d9f8ca6a651.
Report an issue: GitHub.