{"id":"035e40a06ec60466","repo":"apache/kafka","slug":"topic-topic-is-invalid-035e40","errorCode":null,"errorMessage":"Topic '${topic}' is invalid","messagePattern":"Topic '(.+?)' is invalid","errorType":"exception","errorClass":"InvalidTopicException","httpStatus":null,"severity":"error","filePath":"clients/src/main/java/org/apache/kafka/clients/consumer/internals/TopicMetadataRequestManager.java","lineNumber":260,"sourceCode":"            Cluster cluster = response.buildCluster();\n\n            final Set<String> unauthorizedTopics = cluster.unauthorizedTopics();\n            if (!unauthorizedTopics.isEmpty())\n                throw new TopicAuthorizationException(unauthorizedTopics);\n\n            Map<String, Errors> errors = response.errors();\n            if (!errors.isEmpty()) {\n                // if there were errors, we need to check whether they were fatal or whether\n                // we should just retry\n\n                log.debug(\"Topic metadata fetch included errors: {}\", errors);\n\n                for (Map.Entry<String, Errors> errorEntry : errors.entrySet()) {\n                    String topic = errorEntry.getKey();\n                    Errors error = errorEntry.getValue();\n\n                    if (error == Errors.INVALID_TOPIC_EXCEPTION)\n                        throw new InvalidTopicException(\"Topic '\" + topic + \"' is invalid\");\n                    else if (error == Errors.UNKNOWN_TOPIC_OR_PARTITION)\n                        // if a requested topic is unknown, we just continue and let it be absent\n                        // in the returned map\n                        continue;\n                    else if (error.exception() instanceof RetriableException)\n                        throw error.exception();\n                    else\n                        throw new KafkaException(\"Unexpected error fetching metadata for topic \" + topic,\n                            error.exception());\n                }\n            }\n\n            HashMap<String, List<PartitionInfo>> topicsPartitionInfos = new HashMap<>();\n            for (String topic : cluster.topics())\n                topicsPartitionInfos.put(topic, cluster.partitionsForTopic(topic));\n            return topicsPartitionInfos;\n        }\n","sourceCodeStart":242,"sourceCodeEnd":278,"githubUrl":"https://github.com/apache/kafka/blob/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/clients/consumer/internals/TopicMetadataRequestManager.java#L242-L278","documentation":"Async-consumer counterpart of error 233. Thrown as InvalidTopicException by TopicMetadataRequestManager.handleTopicMetadataResponse when the MetadataResponse errors map contains Errors.INVALID_TOPIC_EXCEPTION for a topic. The new consumer network thread surfaces a server-side topic-name validation failure as a hard, non-retriable exception, propagated through the inflightRequests CompletableFuture.","triggerScenarios":"Calling any metadata-triggering operation (partitionsFor, listTopics, subscribe) with a topic name that fails broker-side validation (illegal characters, length > 249, or '.' / '..' literal). The new consumer surfaces the same INVALID_TOPIC_EXCEPTION code as the legacy path.","commonSituations":"Templated or env-var-derived topic names containing '/', ':', or spaces; topic name auto-created by another producer with a typo; dynamic naming producing > 249 chars; mixed environments where one cluster tolerates a name another rejects.","solutions":["Sanitize the topic name to ^[a-zA-Z0-9\\._-]{1,249}$ and avoid the literals '.' and '..'.","Verify the topic actually exists with kafka-topics --describe; if a stray invalid topic was auto-created, delete and recreate with a valid name.","Disable auto.create.topics.enable on the broker to prevent silent creation of bad names.","Patch whatever templating/interpolation produced the bad name so future runs are clean."],"exampleFix":"// before\nString topic = env.get(\"TOPIC\"); // \"orders/eur\" -> illegal\nconsumer.partitionsFor(topic);\n\n// after\nString raw = env.get(\"TOPIC\");\nString topic = raw.replaceAll(\"[^a-zA-Z0-9._-]\", \"_\");\nconsumer.partitionsFor(topic);","handlingStrategy":"validation","validationCode":"// Validate topic name up front (same rule Kafka enforces server-side).\nstatic final java.util.regex.Pattern NAME = java.util.regex.Pattern.compile(\"^[a-zA-Z0-9._-]+$\");\nstatic boolean isValidTopicName(String t) {\n    return t != null && t.length() >= 1 && t.length() <= 249\n        && !t.equals(\".\") && !t.equals(\"..\")\n        && NAME.matcher(t).matches();\n}\nif (!isValidTopicName(topic)) throw new IllegalArgumentException(\"bad topic name: \" + topic);","typeGuard":"/** Narrows a String to a Kafka-valid topic name. */\nstatic boolean isValidTopicName(String topic) {\n    if (topic == null || topic.isEmpty() || topic.length() > 249) return false;\n    if (topic.equals(\".\") || topic.equals(\"..\")) return false;\n    for (int i = 0; i < topic.length(); i++) {\n        char c = topic.charAt(i);\n        boolean ok = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')\n            || (c >= '0' && c <= '9') || c == '.' || c == '_' || c == '-';\n        if (!ok) return false;\n    }\n    return true;\n}","tryCatchPattern":"try {\n    consumer.subscribe(java.util.Set.of(topic));\n} catch (org.apache.kafka.common.errors.InvalidTopicException e) {\n    log.warn(\"Rejected invalid topic name '{}'; correct source/config\", topic);\n    throw e; // name will not self-correct — do not retry unchanged\n}","preventionTips":["Validate topic names at the configuration boundary before they reach the client.","Centralize topic-name construction behind a factory that enforces length and charset.","Reject '.', '..', and any path-like or control characters explicitly.","Add a config-load test that fails the deploy if any topic name violates the rule."],"tags":["kafka","consumer","async","topic-name","validation","metadata"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}