flowable/flowable-engine · error · FlowableIllegalArgumentException

topic in topic partition in channel model […

Error message

topic in topic partition in channel model [ <channelModel.getKey()> ] must resolve to a non empty string

What it means

When a channel uses explicit topic partitions, each TopicPartition entry must have a topic that resolves to a non-empty String. resolveTopicPartitions first calls resolveExpressionAsString and then checks StringUtils.hasText; blank or null topics cause FlowableIllegalArgumentException with the channel key in the message.

Solutions

  1. Set an actual topic name for each topicPartitions entry, e.g. topic: orders.
  2. Fix the placeholder/environment variable so it resolves to a non-empty value.
  3. Remove the empty topicPartition entry if it is not needed.
  4. Add a startup validation that all referenced placeholders resolve before deploying the model.

Example fix

// before
topicPartitions:
  - topic: ${KAFKA_ORDERS_TOPIC}   # env var unset -> empty
    partitions: ["0"]

// after
topicPartitions:
  - topic: ${KAFKA_ORDERS_TOPIC:orders}
    partitions: ["0"]
Defensive patterns

Strategy: validation

Validate before calling

for (var tp : channel.getTopicPartitions()) {
    if (tp.getTopic() == null || tp.getTopic().isBlank())
        throw new IllegalArgumentException("topicPartitions[].topic must be non-empty for channel " + channel.getKey());
}

Try / catch

try { ... } catch (FlowableIllegalArgumentException e) { if (e.getMessage().contains("must resolve to a non empty string")) { log.error("Topic partition topic missing: {}", e.getMessage()); } else throw e; }

Prevention

When it happens

Trigger: A topicPartitions[].topic attribute is an expression resolving to null/empty/whitespace String while createKafkaListenerEndpoint processes the channel's topic partition list.

Common situations: Placeholder for topic name not set in environment (resolves to empty); topic key left blank in channel model; expression references a property that exists but is empty string; YAML indentation mistake making topic null.

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


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/4f1d5980c9a50dc1. Report an issue: GitHub.

Appendix: source

Thrown at modules/flowable-event-registry-spring/src/main/java/org/flowable/eventregistry/spring/kafka/KafkaChannelDefinitionProcessor.java:593

                throw new IllegalStateException(
                    "topicPattern in channel model [ " + channelModel + " ] must resolve to a Pattern or String, not " + resolved.getClass());
            }
        }

        return pattern;
    }

    protected Collection<TopicPartitionOffset> resolveTopicPartitions(KafkaInboundChannelModel channelModel) {
        Collection<KafkaInboundChannelModel.TopicPartition> topicPartitions = channelModel.getTopicPartitions();
        if (topicPartitions == null || topicPartitions.isEmpty()) {
            return Collections.emptyList();
        }

        List<TopicPartitionOffset> tps = new ArrayList<>();
        for (KafkaInboundChannelModel.TopicPartition topicPartition : topicPartitions) {
            String topic = resolveExpressionAsString(topicPartition.getTopic(), "topicPartitions[].topic");
            if (!StringUtils.hasText(topic)) {
                throw new FlowableIllegalArgumentException(
                        "topic in topic partition in channel model [ " + channelModel.getKey() + " ] must resolve to a non empty string");
            }

            Collection<String> partitions = topicPartition.getPartitions();
            if (partitions == null || partitions.isEmpty()) {
                throw new FlowableIllegalArgumentException(
                        "partitions in topic partition in channel model [ " + channelModel.getKey() + " ] must not be empty");
            }

            for (String partition : partitions) {
                resolvePartitionAsInteger(topic, resolveExpression(partition), tps);
            }

        }

        return tps;
    }

View on GitHub (pinned to d6d39ce1c6)