{"id":"f4806fc797472085","repo":"apache/kafka","slug":"topic-collection-to-subscribe-to-cannot-contain-nu","errorCode":null,"errorMessage":"Topic collection to subscribe to cannot contain null or empty topic","messagePattern":"Topic collection to subscribe to cannot contain null or empty topic","errorType":"validation","errorClass":"java.lang.IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"clients/src/main/java/org/apache/kafka/clients/consumer/internals/AsyncKafkaConsumer.java","lineNumber":2304,"sourceCode":"        }\n        if (subscriptionPattern.pattern().isEmpty()) {\n            throw new IllegalArgumentException(\"Topic pattern to subscribe to cannot be empty\");\n        }\n    }\n\n    private void subscribeInternal(Collection<String> topics, Optional<ConsumerRebalanceListener> listener) {\n        acquireAndEnsureOpen();\n        try {\n            throwIfGroupIdNotDefined();\n            if (topics == null)\n                throw new IllegalArgumentException(\"Topic collection to subscribe to cannot be null\");\n            if (topics.isEmpty()) {\n                // treat subscribing to empty topic list as the same as unsubscribing\n                unsubscribe();\n            } else {\n                for (String topic : topics) {\n                    if (isBlank(topic))\n                        throw new IllegalArgumentException(\"Topic collection to subscribe to cannot contain null or empty topic\");\n                }\n\n                // Clear the buffered data which are not a part of newly assigned topics\n                final Set<TopicPartition> currentTopicPartitions = new HashSet<>();\n\n                for (TopicPartition tp : subscriptions.assignedPartitions()) {\n                    if (topics.contains(tp.topic()))\n                        currentTopicPartitions.add(tp);\n                }\n\n                fetchBuffer.retainAll(currentTopicPartitions);\n                log.info(\"Subscribed to topic(s): {}\", String.join(\", \", topics));\n                applicationEventHandler.addAndGet(new TopicSubscriptionChangeEvent(\n                    new HashSet<>(topics),\n                    listener,\n                    defaultApiTimeoutDeadlineMs()\n                ));\n            }","sourceCodeStart":2286,"sourceCodeEnd":2322,"githubUrl":"https://github.com/apache/kafka/blob/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/clients/consumer/internals/AsyncKafkaConsumer.java#L2286-L2322","documentation":"Thrown by AsyncKafkaConsumer.subscribeInternal when at least one element of the topics collection is null, empty, or whitespace (via isBlank). The async consumer validates every topic name before enqueuing a TopicSubscriptionChangeEvent, because the background thread cannot report a malformed topic back to the caller cleanly. It is the per-element counterpart to the null-collection guard.","triggerScenarios":"Calling consumer.subscribe(Arrays.asList(\"orders\", \"\", \"payments\")), consumer.subscribe(Arrays.asList(\"orders\", null)), or passing a collection that contains a whitespace-only string like \"  \". Also hit when topic names are read from a config file with trailing empty lines split into list entries.","commonSituations":"Parsing topics from a comma-separated config property where an empty token slips through (e.g. \"orders,,payments\"); deserializing a JSON/YAML list that includes nulls; environment-variable overrides that resolve to an empty string in one slot; copy-paste of topic lists with a dangling comma.","solutions":["Sanitize the topic list before subscribe: topics.removeIf(s -> s == null || s.trim().isEmpty()); and log what was dropped.","Fix the source of the list: trim each token when splitting the config string, e.g. Arrays.stream(raw.split(\",\")).map(String::trim).filter(s -> !s.isEmpty()).collect(toList()).","Validate at application startup and fail fast with a clear error naming the offending topic name.","Add a test for the sanitized list to prevent regressions."],"exampleFix":"// before\nString raw = config.get(\"topics\"); // \"orders,,payments\"\nconsumer.subscribe(Arrays.asList(raw.split(\",\")));\n\n// after\nList<String> topics = Arrays.stream(raw.split(\",\"))\n    .map(String::trim)\n    .filter(s -> !s.isEmpty())\n    .collect(Collectors.toList());\nconsumer.subscribe(topics);","handlingStrategy":"validation","validationCode":"// Strip blanks before subscribing.\nCollection<String> clean = topics == null\n    ? List.of()\n    : topics.stream()\n           .filter(t -> t != null && !t.trim().isEmpty())\n           .collect(Collectors.toList());\nif (!topics.isEmpty() && clean.isEmpty()) {\n    throw new IllegalArgumentException(\"All provided topics were null/blank\");\n}\nconsumer.subscribe(clean);","typeGuard":"// Predicate-based element guard.\nstatic boolean hasNoBlankTopics(Collection<String> topics) {\n    return topics != null && topics.stream().allMatch(t -> t != null && !t.trim().isEmpty());\n}","tryCatchPattern":"try {\n    consumer.subscribe(topics);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"null or empty topic\")) {\n        topics.removeIf(t -> t == null || t.trim().isEmpty());\n        consumer.subscribe(topics); // retry with cleaned set\n    } else {\n        throw e;\n    }\n}","preventionTips":["Use a Set<String> populated from a controlled source (config file / constants) rather than ad-hoc user input.","Sanitize topic names at the boundary where they enter your code, not at the Kafka call site.","Kafka topic names must match ^[a-zA-Z0-9._-]+$ ; validate that pattern early.","Add a unit test that asserts subscribe rejects \"\" and \"   \"."],"tags":["kafka","consumer","async-consumer","subscribe","validation","configuration"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}