flowable/flowable-engine · error · FlowableException
expected expression to resolve to but it did not. Resolved…
Error message
expected expression <expression> to resolve to <type> but it did not. Resolved value is <value>
What it means
resolveExpression evaluates a Spring EL expression against the expression context and casts the result to the expected type. If the value is not an instance of the requested type (e.g. expected KafkaPartitionProvider but got a String), this FlowableException is thrown. It is a type mismatch between what the expression returns and what the channel processing expects.
Solutions
- Make the expression return the exact type expected, e.g. a bean implementing KafkaPartitionProvider.
- Check the resolved bean's type with a debugger/log or by inspecting the Spring context.
- Change the attribute: use eventField/fixedValue for plain values instead of delegateExpression.
- Note the message prints the resolved value and the expected type name — match them.
Example fix
// before
@Bean public String partitionProvider() { return "0"; }
// after
@Bean public KafkaPartitionProvider partitionProvider() { return ignore -> 0; } Defensive patterns
Strategy: validation
Validate before calling
Object bean = applicationContext.getBean(beanName);
if (!(bean instanceof KafkaPartitionProvider) && !(bean instanceof KafkaMessageKeyProvider)) {
throw new IllegalStateException(beanName + " does not implement the required Kafka provider interface");
} Type guard
boolean isExpectedProvider(Object v, Class<T> type) { return type.isInstance(v); } Try / catch
try {
deployChannel(channelModel);
} catch (FlowableException e) {
if (e.getMessage() != null && e.getMessage().startsWith("expected expression")) {
log.error("Expression resolved to wrong type: " + e.getMessage(), e);
} else { throw e; }
} Prevention
- Make delegateExpression beans implement the exact provider interface Flowable expects
- Keep provider bean names unambiguous and typed
- Read the exception message: it names the expected type and the resolved value
When it happens
Trigger: A delegateExpression attribute (e.g. for KafkaPartitionProvider, KafkaMessageKeyProvider, KafkaTopicProvider) resolves to a bean/value of a different type than required.
Common situations: Pointing delegateExpression at a plain String bean or a wrong provider class; typo'd bean name resolving to another bean of a different type; refactor renamed a provider class.
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
- Channel definition cannot resolve as a String[] or a String
- partition in TopicPartition for topic
- The [ ] must resolve to a Boolean or a String that can be…
- The [ ] must resolve to a String. Resolved to [ ] for [ ]
- The [ ] must resolve to an Number or a String that can be…
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/4828d573033c77f9.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-event-registry-spring/src/main/java/org/flowable/eventregistry/spring/kafka/KafkaChannelDefinitionProcessor.java:732
} else if (StringUtils.hasText(recordKey.getDelegateExpression())) {
return resolveExpression(recordKey.getDelegateExpression(), KafkaMessageKeyProvider.class);
} else if (recordKey.getFixedValue() != null) {
String fixedValue = org.apache.commons.lang3.StringUtils.defaultIfBlank(recordKey.getFixedValue(), null);
return ignore -> fixedValue;
} else {
throw new FlowableException(
"The kafka recordKey value was not found for the channel model with key " + channelModel.getKey()
+ ". One of fixedValue, delegateExpression or eventField should be set.");
}
}
protected <T> T resolveExpression(String expression, Class<T> type) {
Object value = this.resolver.evaluate(expression, this.expressionContext);
if (type.isInstance(value)) {
return type.cast(value);
}
throw new FlowableException("expected expression " + expression + " to resolve to " + type + " but it did not. Resolved value is " + value);
}
protected Object resolveExpression(String value) {
String resolvedValue = resolve(value);
return this.resolver.evaluate(resolvedValue, this.expressionContext);
}
@SuppressWarnings("unchecked")
protected GenericMessageListener<ConsumerRecord<Object, Object>> createMessageListener(EventRegistry eventRegistry, InboundChannelModel inboundChannelModel) {
@SuppressWarnings("rawtypes")
GenericMessageListener kafkaChannelMessageListenerAdapter = new KafkaChannelMessageListenerAdapter(eventRegistry, inboundChannelModel);
return kafkaChannelMessageListenerAdapter;
}
@Override
public void unregisterChannelModel(ChannelModel channelModel, String tenantId, EventRepositoryService eventRepositoryService) {View on GitHub (pinned to d6d39ce1c6)