flowable/flowable-engine · error · IllegalStateException
The [ ] must resolve to a String. Resolved to [ ] for [ ]
Error message
The [<attribute>] must resolve to a String. Resolved to [<class>] for [<value>]
What it means
resolveExpressionAsString resolves a channel attribute and requires it to be null or a String; anything else (Number, Boolean, collection, bean) throws IllegalStateException naming the attribute, resolved class, and original value. It is used for topic, groupId, and other string channel settings, so almost any mis-typed Kafka channel attribute can surface here.
Solutions
- Quote the value so it resolves as a String, e.g. groupId: "12345" in YAML/JSON model.
- Check SpEL evaluation: use '...' literals for numeric-looking topic/group names.
- Fix the backing property type to String in configuration/beans.
- If programmatic, call String.valueOf(...) when building the channel model attribute.
Example fix
// before (channel model JSON/YAML) "groupId": 12345 // after "groupId": "12345"
Defensive patterns
Strategy: type-guard
Validate before calling
Object v = resolveExpression(value); if (v != null && !(v instanceof String)) throw new IllegalArgumentException(attribute + " must resolve to a String, got " + v.getClass());
Type guard
boolean isStringLike(Object v) { return v == null || v instanceof String; } Try / catch
try { processor.createKafkaListenerEndpoint(...); } catch (IllegalStateException e) { if (e.getMessage().contains("must resolve to a String")) { log.error("Channel attribute must be a String: {}", e.getMessage()); } else throw e; } Prevention
- Quote all channel model string values, especially numeric topic/group names
- Use SpEL string literals ('...') for expressions
- Type backing config properties as String
- Validate channel models with a schema before deployment
When it happens
Trigger: Any String attribute of the Kafka channel model (topic, groupId, properties values, sameIntervalTopicReuseStrategy, listener endpoint fields) resolves via an expression to a non-String object during createKafkaListenerEndpoint or resolveProperties/resolveRetryConfiguration.
Common situations: groupId set to a numeric literal in the channel model (groupId: 12345 resolves to Integer); topic name all digits resolved as Number by SpEL; property placeholder returning an Integer from YAML; passing a raw object instead of a string in programmatic model building.
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
- The [ ] must resolve to a Boolean or a String that can be…
- The [ ] must resolve to an Number or a String that can be…
- The [ ] must resolve to an Number or a String that can be…
- topicPattern in channel model
- Channel definition cannot resolve as a String[] or a String
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/30e65417205a8341.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-event-registry-spring/src/main/java/org/flowable/eventregistry/spring/kafka/KafkaChannelDefinitionProcessor.java:526
} else if (resolved instanceof Boolean) {
result = (Boolean) resolved;
} else if (resolved != null) {
throw new IllegalStateException(
"The [" + attribute + "] must resolve to a Boolean or a String that can be parsed as a Boolean. "
+ "Resolved to [" + resolved.getClass() + "] for [" + value + "]");
}
return result;
}
protected String resolveExpressionAsString(String value, String attribute) {
if (!StringUtils.hasLength(value)) {
return null;
}
Object resolved = resolveExpression(value);
if (resolved instanceof String) {
return (String) resolved;
} else {
throw new IllegalStateException("The [" + attribute + "] must resolve to a String. "
+ "Resolved to [" + resolved.getClass() + "] for [" + value + "]");
}
}
protected Collection<String> resolveTopics(KafkaInboundChannelModel channelDefinition) {
Collection<String> topics = channelDefinition.getTopics();
if (topics == null || topics.isEmpty()) {
return Collections.emptyList();
}
List<String> resultTopics = new ArrayList<>();
for (String queue : topics) {
resolveTopics(resolveExpression(queue), resultTopics, channelDefinition);
}
return resultTopics;View on GitHub (pinned to d6d39ce1c6)