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

  1. Make the expression return the exact type expected, e.g. a bean implementing KafkaPartitionProvider.
  2. Check the resolved bean's type with a debugger/log or by inspecting the Spring context.
  3. Change the attribute: use eventField/fixedValue for plain values instead of delegateExpression.
  4. 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

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


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)