{"record":{"id":"2aba9118a5457328","repo":"nats-io/nats-server","slug":"character-not-supported-for-mqtt-topics","errorCode":null,"errorMessage":"character not supported for MQTT topics","messagePattern":"character not supported for MQTT topics","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/mqtt.go","lineNumber":243,"sourceCode":"\terrMQTTNotWebsocketPort           = errors.New(\"MQTT clients over websocket must connect to the Websocket port, not the MQTT port\")\n\terrMQTTTopicFilterCannotBeEmpty   = errors.New(\"topic filter cannot be empty\")\n\terrMQTTMalformedVarInt            = errors.New(\"malformed variable int\")\n\terrMQTTSecondConnectPacket        = errors.New(\"received a second CONNECT packet\")\n\terrMQTTServerNameMustBeSet        = errors.New(\"mqtt requires server name to be explicitly set\")\n\terrMQTTUserMixWithUsersNKeys      = errors.New(\"mqtt authentication username not compatible with presence of users/nkeys\")\n\terrMQTTTokenMixWIthUsersNKeys     = errors.New(\"mqtt authentication token not compatible with presence of users/nkeys\")\n\terrMQTTAckWaitMustBePositive      = errors.New(\"ack wait must be a positive value\")\n\terrMQTTJSAPITimeoutMustBePositive = errors.New(\"JS API timeout must be a positive value\")\n\terrMQTTStandaloneNeedsJetStream   = errors.New(\"mqtt requires JetStream to be enabled if running in standalone mode\")\n\terrMQTTConnFlagReserved           = errors.New(\"connect flags reserved bit not set to 0\")\n\terrMQTTWillAndRetainFlag          = errors.New(\"if Will flag is set to 0, Will Retain flag must be 0 too\")\n\terrMQTTPasswordFlagAndNoUser      = errors.New(\"password flag set but username flag is not\")\n\terrMQTTCIDEmptyNeedsCleanFlag     = errors.New(\"when client ID is empty, clean session flag must be set to 1\")\n\terrMQTTEmptyWillTopic             = errors.New(\"empty Will topic not allowed\")\n\terrMQTTEmptyUsername              = errors.New(\"empty user name not allowed\")\n\terrMQTTTopicIsEmpty               = errors.New(\"topic cannot be empty\")\n\terrMQTTPacketIdentifierIsZero     = errors.New(\"packet identifier cannot be 0\")\n\terrMQTTUnsupportedCharacters      = errors.New(\"character not supported for MQTT topics\")\n\terrMQTTInvalidSession             = errors.New(\"invalid MQTT session\")\n\terrMQTTInvalidRetainFlags         = errors.New(\"invalid retained message flags\")\n\terrMQTTInvalidRetainedMessage     = errors.New(\"invalid retained message\")\n\terrMQTTSessionCollision           = errors.New(\"stored session does not match client ID\")\n\terrMQTTInvalidPublishLength       = errors.New(\"invalid publish message, variable header exceeds remaining length\")\n\terrMQTTAckPipelineStopped         = errors.New(\"QoS1 PUBACK pipeline has shut down while admitting a message, \" +\n\t\t\"abandoning the wait for its JetStream ack; failing the connection, \" +\n\t\t\"the client will re-send unacknowledged PUBLISH packets on reconnect\")\n)\n\ntype srvMQTT struct {\n\tlistener     net.Listener\n\tlistenerErr  error\n\tauthOverride bool\n\tsessmgr      mqttSessionManager\n}\n\ntype mqttSessionManager struct {","sourceCodeStart":225,"sourceCodeEnd":261,"githubUrl":"https://github.com/nats-io/nats-server/blob/3a66a489d262bf89b71a71c955c94920394532f3/server/mqtt.go#L225-L261","documentation":"errMQTTUnsupportedCharacters is returned when a topic (or subject) contains characters that are not allowed in MQTT topics. Specific bytes such as 0x7f (DEL, used internally as a SubjectTree pivot marker) and other control/unsupported characters are rejected to keep the subject tree consistent and to prevent control-line splitting when subjects are forwarded to other connection types (e.g. leaf nodes). Thrown from the topic-character validation switch around server/mqtt.go:6234-6240.","triggerScenarios":"Publishing or subscribing to a topic containing DEL (0x7f), other control bytes, or characters forbidden for MQTT topics; retained-message recovery hits such a character and the validation returns the error.","commonSituations":"Applications interpolating raw binary payloads, filenames, or user input into topic names; topics built from strings containing '\\x7f' or newline/control characters; legacy data in a retained-message stream containing unsupported bytes.","solutions":["Sanitize topics before use: strip or replace DEL and control characters, and keep topics to allowed MQTT characters.","Restrict topic names to [A-Za-z0-9] plus '/', '_', '-' style safe sets in your publish/subscribe call sites.","Purge or re-publish retained messages stored with unsupported characters."],"exampleFix":"// before\nnc.Publish(userInput+\"/status\", data)\n// after\ntopic := sanitizeTopic(userInput) // remove DEL/control chars, reject empty\nnc.Publish(topic+\"/status\", data)","handlingStrategy":"validation","validationCode":"func sanitizeTopic(topic string) (string, error) {\n    for _, r := range topic {\n        if r == 0x7f || (r < 0x20 && r != '/') {\n            return \"\", fmt.Errorf(\"unsupported character %q in topic\", r)\n        }\n    }\n    return topic, nil\n}","typeGuard":"func hasOnlySafeTopicChars(topic string) bool {\n    for _, r := range topic {\n        if r == 0x7f || (r < 0x20) {\n            return false\n        }\n    }\n    return len(topic) > 0\n}","tryCatchPattern":"if _, err := sanitizeTopic(topic); err != nil {\n    log.Warn(\"rejecting topic with unsupported characters\", \"topic\", topic)\n    return\n}","preventionTips":["Never build topics from raw binary data or untrusted user input without sanitizing","Whitelist allowed topic characters instead of blacklisting","Test retained-message data for control characters when migrating between versions"],"tags":["mqtt","topic-validation","encoding"],"backgroundTag":"invalid-topic-characters","analyzedSha":"3a66a489d262bf89b71a71c955c94920394532f3","analyzedAt":"2026-09-02T04:41:54.247Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}