flowable/flowable-engine · error · FlowableException
Channel model in tenant has retry configuration but no…
Error message
Channel model <key> in tenant <tenantId> has retry configuration but no topics have been provided for it
What it means
KafkaChannelDefinitionProcessor.createEndpointConfigurations() builds retry-topic configurations for a Kafka channel. Retry requires at least one destination topic; if the channel has retry configuration but its resolved topic list is empty, it throws this FlowableException. It prevents silently creating a retry setup with nothing to publish retries to.
Solutions
- Add the required topic(s) to the channel model's topics configuration.
- If retry is not intended, remove the retry configuration from the channel model.
- Check the retry topic configuration (retryTopicConfiguration) specifies destination topic properties with non-empty topic names.
- Validate channel model definitions at deploy time with a pre-check that topics is non-empty when retry is enabled.
Example fix
// before: retry configured, topics missing <channel key="orders" type="kafka"> <retry delays="1000,5000"/> </channel> // after <channel key="orders" type="kafka" topics="orders"> <retry delays="1000,5000"/> </channel>
Defensive patterns
Strategy: validation
Validate before calling
if (channelModel.isRetryEnabled() && (channelModel.getTopics() == null || channelModel.getTopics().isEmpty())) {
throw new IllegalArgumentException("Channel " + channelModel.getKey() + " has retry config but no topics");
} Try / catch
try {
configurations(channelModel, tenantId);
} catch (FlowableException e) {
log.error("Invalid Kafka channel definition: {}", e.getMessage());
} Prevention
- Enforce topics non-empty whenever retry configuration is present in channel templates.
- Validate channel model JSON/XML at deploy time.
- Review channel definitions after converting channels to retry mode.
When it happens
Trigger: Registering/deploying a Kafka channel whose channelModel has retry configuration enabled while getTopics() (main endpoint topics or retry-specified topics) returns an empty collection.
Common situations: Channel model XML/JSON defines retry delays but omits the 'topics' attribute; topics list cleared by a miswritten template; channel converted to retry mode without updating topic configuration.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- At least one of topics, topicPartitions or topicPattern…
- Could not resolve the KafkaListenerContainerFactory to use…
- failedJobRetryTimeCycle has wrong format:" +…
- It is not possible to auto create new topics when no kafka…
- No channel model set for outgoing
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/1dd85dd5d0389c2c.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-event-registry-spring/src/main/java/org/flowable/eventregistry/spring/kafka/KafkaChannelDefinitionProcessor.java:251
if (retryTopicConfiguration != null) {
Collection<String> topics;
if (mainEndpoint.getTopics().isEmpty()) {
TopicPartitionOffset[] topicPartitionsToAssign = mainEndpoint.getTopicPartitionsToAssign();
if (topicPartitionsToAssign != null && topicPartitionsToAssign.length > 0) {
topics = Arrays.stream(topicPartitionsToAssign)
.map(TopicPartitionOffset::getTopic)
.collect(Collectors.toCollection(LinkedHashSet::new));
} else {
topics = Collections.emptyList();
}
} else {
topics = mainEndpoint.getTopics();
}
if (topics.isEmpty()) {
throw new FlowableException("Channel model " + channelModel.getKey() + " in tenant " + tenantId
+ " has retry configuration but no topics have been provided for it");
}
Collection<KafkaChannelDefinitionProcessor.Configuration> configurations = new ArrayList<>(
retryTopicConfiguration.getDestinationTopicProperties().size());
DefaultDestinationTopicResolver topicResolver = new DefaultDestinationTopicResolver(Clock.systemUTC());
topicResolver.setApplicationContext(applicationContext);
DefaultDestinationTopicProcessor processor = new DefaultDestinationTopicProcessor(topicResolver);
ListenerContainerFactoryConfigurer factoryConfigurer = createListenerContainerFactoryConfigurer(retryConfiguration, backOff, topicResolver);
String id = mainEndpoint.getId();
if (id == null) {
id = "no.id.provided";
}
DestinationTopicProcessor.Context context = new DestinationTopicProcessor.Context(id, retryTopicConfiguration.getDestinationTopicProperties());
processor.processDestinationTopicProperties(destinationTopicProperties -> {
Suffixer suffixer = new Suffixer(destinationTopicProperties.suffix());View on GitHub (pinned to d6d39ce1c6)