{"record":{"id":"1dd85dd5d0389c2c","repo":"flowable/flowable-engine","slug":"channel-model-key-in-tenant-tenantid-has-retry","errorCode":null,"errorMessage":"Channel model <key> in tenant <tenantId> has retry configuration but no topics have been provided for it","messagePattern":"Channel model <key> in tenant <tenantId> has retry configuration but no topics have been provided for it","errorType":"exception","errorClass":"FlowableException","httpStatus":null,"severity":"error","filePath":"modules/flowable-event-registry-spring/src/main/java/org/flowable/eventregistry/spring/kafka/KafkaChannelDefinitionProcessor.java","lineNumber":251,"sourceCode":"\n        if (retryTopicConfiguration != null) {\n\n            Collection<String> topics;\n            if (mainEndpoint.getTopics().isEmpty()) {\n                TopicPartitionOffset[] topicPartitionsToAssign = mainEndpoint.getTopicPartitionsToAssign();\n                if (topicPartitionsToAssign != null && topicPartitionsToAssign.length > 0) {\n                    topics = Arrays.stream(topicPartitionsToAssign)\n                            .map(TopicPartitionOffset::getTopic)\n                            .collect(Collectors.toCollection(LinkedHashSet::new));\n                } else {\n                    topics = Collections.emptyList();\n                }\n            } else {\n                topics = mainEndpoint.getTopics();\n            }\n\n            if (topics.isEmpty()) {\n                throw new FlowableException(\"Channel model \" + channelModel.getKey() + \" in tenant \" + tenantId\n                        + \" has retry configuration but no topics have been provided for it\");\n            }\n\n            Collection<KafkaChannelDefinitionProcessor.Configuration> configurations = new ArrayList<>(\n                    retryTopicConfiguration.getDestinationTopicProperties().size());\n\n            DefaultDestinationTopicResolver topicResolver = new DefaultDestinationTopicResolver(Clock.systemUTC());\n            topicResolver.setApplicationContext(applicationContext);\n            DefaultDestinationTopicProcessor processor = new DefaultDestinationTopicProcessor(topicResolver);\n            ListenerContainerFactoryConfigurer factoryConfigurer = createListenerContainerFactoryConfigurer(retryConfiguration, backOff, topicResolver);\n\n            String id = mainEndpoint.getId();\n            if (id == null) {\n                id = \"no.id.provided\";\n            }\n            DestinationTopicProcessor.Context context = new DestinationTopicProcessor.Context(id, retryTopicConfiguration.getDestinationTopicProperties());\n            processor.processDestinationTopicProperties(destinationTopicProperties -> {\n                Suffixer suffixer = new Suffixer(destinationTopicProperties.suffix());","sourceCodeStart":233,"sourceCodeEnd":269,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-event-registry-spring/src/main/java/org/flowable/eventregistry/spring/kafka/KafkaChannelDefinitionProcessor.java#L233-L269","documentation":"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.","triggerScenarios":"Registering/deploying a Kafka channel whose channelModel has retry configuration enabled while getTopics() (main endpoint topics or retry-specified topics) returns an empty collection.","commonSituations":"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.","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."],"exampleFix":"// before: retry configured, topics missing\n<channel key=\"orders\" type=\"kafka\">\n  <retry delays=\"1000,5000\"/>\n</channel>\n\n// after\n<channel key=\"orders\" type=\"kafka\" topics=\"orders\">\n  <retry delays=\"1000,5000\"/>\n</channel>","handlingStrategy":"validation","validationCode":"if (channelModel.isRetryEnabled() && (channelModel.getTopics() == null || channelModel.getTopics().isEmpty())) {\n    throw new IllegalArgumentException(\"Channel \" + channelModel.getKey() + \" has retry config but no topics\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    configurations(channelModel, tenantId);\n} catch (FlowableException e) {\n    log.error(\"Invalid Kafka channel definition: {}\", e.getMessage());\n}","preventionTips":["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."],"tags":["kafka","retry","configuration","channel-model"],"backgroundTag":"empty-required-field","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-18T11:17:12.947Z"}