{"id":"4c4372b9bc66b974","repo":"apache/kafka","slug":"topic-name-is-invalid-reasoninvalid","errorCode":null,"errorMessage":"Topic name is invalid: {reasonInvalid}","messagePattern":"Topic name is invalid: (.+?)","errorType":"validation","errorClass":"InvalidTopicException","httpStatus":null,"severity":"error","filePath":"clients/src/main/java/org/apache/kafka/common/internals/Topic.java","lineNumber":43,"sourceCode":"public class Topic {\n\n    public static final String GROUP_METADATA_TOPIC_NAME = \"__consumer_offsets\";\n    public static final String TRANSACTION_STATE_TOPIC_NAME = \"__transaction_state\";\n    public static final String SHARE_GROUP_STATE_TOPIC_NAME = \"__share_group_state\";\n    public static final String CLUSTER_METADATA_TOPIC_NAME = \"__cluster_metadata\";\n    public static final TopicPartition CLUSTER_METADATA_TOPIC_PARTITION = new TopicPartition(\n        CLUSTER_METADATA_TOPIC_NAME,\n        0\n    );\n    public static final String LEGAL_CHARS = \"[a-zA-Z0-9._-]\";\n\n    private static final Set<String> INTERNAL_TOPICS = Set.of(GROUP_METADATA_TOPIC_NAME, TRANSACTION_STATE_TOPIC_NAME, SHARE_GROUP_STATE_TOPIC_NAME);\n\n    private static final int MAX_NAME_LENGTH = 249;\n\n    public static void validate(String topic) {\n        validate(topic, \"Topic name\", message -> {\n            throw new InvalidTopicException(message);\n        });\n    }\n\n    private static String detectInvalidTopic(String name) {\n        if (name.isEmpty())\n            return \"the empty string is not allowed\";\n        if (\".\".equals(name))\n            return \"'.' is not allowed\";\n        if (\"..\".equals(name))\n            return \"'..' is not allowed\";\n        if (name.length() > MAX_NAME_LENGTH)\n            return \"the length of '\" + name + \"' is longer than the max allowed length \" + MAX_NAME_LENGTH;\n        if (!containsValidPattern(name))\n            return \"'\" + name + \"' contains one or more characters other than \" +\n                \"ASCII alphanumerics, '.', '_' and '-'\";\n        return null;\n    }\n","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/apache/kafka/blob/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/common/internals/Topic.java#L25-L61","documentation":"Thrown by Topic.validate(topic) when a topic name fails Kafka's naming rules, surfaced as an InvalidTopicException. The validation rejects empty strings, \".\" and \"..\", names longer than 249 characters, and any character outside ASCII alphanumerics plus '.', '_', '-'. The library enforces this on essentially every API that creates, produces to, or assigns a topic so that illegal names fail fast at the client rather than at the broker.","triggerScenarios":"Calling KafkaProducer.send() with a ProducerRecord whose topic name is empty, contains a '/' or space, exceeds 249 chars, or is literally \".\" / \"..\". Also triggered by AdminClient.createTopics(), consumer.subscribe(), KafkaStreams builder.topic(), and assignment APIs — all of which delegate to Topic.validate(). The error message is the logPrefix (\"Topic name\") followed by the specific reason from detectInvalidTopic().","commonSituations":"Topic name built from untrusted input (URL path, table name, tenant id) containing '/', ':', '@', spaces, or Unicode; null/empty string passed because a config lookup or env var was missing; topic name truncated to \".\" or \"..\" by a buggy string transform; very long dynamically-generated names (e.g. '<tenant>.<schema>.<table>.<stream>') exceeding 249 chars; misconfigured connect converter reading topic from a record field that is null.","solutions":["Sanitize the topic name before use: trim, replace any character outside [a-zA-Z0-9._-] with '_' or '-', and collapse empty/null to a known default.","Enforce the 249-character max length in your topic-naming layer and fail with a clear application error before the value reaches the Kafka client.","Reject null/blank topic inputs at your config or request boundary (Objects.requireNonNull / StringUtils.isBlank) so the real source of the missing value is obvious.","If a tenant or schema identifier legitimately contains '/', '.', or ':', map it through a deterministic encoding scheme before forming the Kafka topic name.","Re-read the message suffix — it states the exact reason ('the empty string is not allowed', 'contains one or more characters other than...', length violation) which points directly at which rule was broken."],"exampleFix":"// before — untrusted value used directly\nString topic = request.getPath();          // e.g. \"orders/eu-west-1\"\nproducer.send(new ProducerRecord<>(topic, key, value));   // throws InvalidTopicException\n\n// after — normalize at the boundary\nstatic String safeTopic(String raw) {\n    if (raw == null || raw.isBlank()) throw new IllegalArgumentException(\"topic required\");\n    String t = raw.trim().replaceAll(\"[^a-zA-Z0-9._-]\", \"_\");\n    if (t.length() > 249) throw new IllegalArgumentException(\"topic too long: \" + raw);\n    return t;\n}\nproducer.send(new ProducerRecord<>(safeTopic(request.getPath()), key, value));","handlingStrategy":"validation","validationCode":"// Validate before using the topic name in any API call.\nimport org.apache.kafka.common.internals.Topic;\nimport org.apache.kafka.common.errors.InvalidTopicException;\n\nString topic = ...;\nif (!Topic.isValid(topic)) {\n    // reject at the boundary; never pass to producer/consumer/admin.\n    throw new IllegalArgumentException(\"Refusing to use invalid topic: \" + topic);\n}\n// Use org.apache.kafka.common.internals.Topic.validate(topic) if you prefer InvalidTopicException.","typeGuard":"// Narrow user-supplied strings to 'valid Kafka topic name'.\nstatic String requireValidTopic(String name) {\n    if (name == null || !Topic.isValid(name)) {\n        throw new IllegalArgumentException(\"Invalid Kafka topic name: \" + name);\n    }\n    return name;\n}","tryCatchPattern":"try {\n    Topic.validate(topic);\n} catch (InvalidTopicException e) {\n    // turn a validation failure into a user-facing 400 / reject the request.\n    handleBadRequest(e.getMessage());\n}","preventionTips":["Topic names must match [a-zA-Z0-9._-], be non-empty, not equal '.' or '..', and be at most 249 characters — validate at every input boundary.","Prefer building topic names from an allow-list of known identifiers rather than free-form user input.","Avoid both '.' and '_' in topic names when possible; they collide in JMX metric names (see Topic.hasCollisionChars).","Validate names once at ingestion, not on every produce/consume call, to keep hot paths allocation-free."],"tags":["kafka-clients","topic-naming","validation","producer","consumer","admin"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}