nats-io/nats-server · error

topic cannot be empty

Error message

topic cannot be empty

What it means

errMQTTTopicIsEmpty is returned when an MQTT PUBLISH packet is parsed and its topic name has zero length. The MQTT spec requires every PUBLISH to carry a non-empty topic string, so the server rejects the packet at parse time instead of forwarding a message with no destination. It is thrown from mqttParsePub (server/mqtt.go:4264) when len(pp.topic)==0 after decoding the variable header.

Source

Thrown at server/mqtt.go:241

var (
	errMQTTNotWebsocketPort           = errors.New("MQTT clients over websocket must connect to the Websocket port, not the MQTT port")
	errMQTTTopicFilterCannotBeEmpty   = errors.New("topic filter cannot be empty")
	errMQTTMalformedVarInt            = errors.New("malformed variable int")
	errMQTTSecondConnectPacket        = errors.New("received a second CONNECT packet")
	errMQTTServerNameMustBeSet        = errors.New("mqtt requires server name to be explicitly set")
	errMQTTUserMixWithUsersNKeys      = errors.New("mqtt authentication username not compatible with presence of users/nkeys")
	errMQTTTokenMixWIthUsersNKeys     = errors.New("mqtt authentication token not compatible with presence of users/nkeys")
	errMQTTAckWaitMustBePositive      = errors.New("ack wait must be a positive value")
	errMQTTJSAPITimeoutMustBePositive = errors.New("JS API timeout must be a positive value")
	errMQTTStandaloneNeedsJetStream   = errors.New("mqtt requires JetStream to be enabled if running in standalone mode")
	errMQTTConnFlagReserved           = errors.New("connect flags reserved bit not set to 0")
	errMQTTWillAndRetainFlag          = errors.New("if Will flag is set to 0, Will Retain flag must be 0 too")
	errMQTTPasswordFlagAndNoUser      = errors.New("password flag set but username flag is not")
	errMQTTCIDEmptyNeedsCleanFlag     = errors.New("when client ID is empty, clean session flag must be set to 1")
	errMQTTEmptyWillTopic             = errors.New("empty Will topic not allowed")
	errMQTTEmptyUsername              = errors.New("empty user name not allowed")
	errMQTTTopicIsEmpty               = errors.New("topic cannot be empty")
	errMQTTPacketIdentifierIsZero     = errors.New("packet identifier cannot be 0")
	errMQTTUnsupportedCharacters      = errors.New("character not supported for MQTT topics")
	errMQTTInvalidSession             = errors.New("invalid MQTT session")
	errMQTTInvalidRetainFlags         = errors.New("invalid retained message flags")
	errMQTTInvalidRetainedMessage     = errors.New("invalid retained message")
	errMQTTSessionCollision           = errors.New("stored session does not match client ID")
	errMQTTInvalidPublishLength       = errors.New("invalid publish message, variable header exceeds remaining length")
	errMQTTAckPipelineStopped         = errors.New("QoS1 PUBACK pipeline has shut down while admitting a message, " +
		"abandoning the wait for its JetStream ack; failing the connection, " +
		"the client will re-send unacknowledged PUBLISH packets on reconnect")
)

type srvMQTT struct {
	listener     net.Listener
	listenerErr  error
	authOverride bool
	sessmgr      mqttSessionManager
}

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Fix the client so it writes a valid, non-empty topic (length-prefixed) into the PUBLISH variable header before sending.
  2. Validate the topic string on the publishing side before constructing the packet (e.g. reject empty strings early).
  3. If you are generating test/protocol bytes, include the topic length prefix plus topic bytes, e.g. []byte{0, 3, 'a', '/', 'b'}.

Example fix

// before: client sends PUBLISH with empty topic
buf := []byte{0x30, remLen, 0x00, 0x00 /* zero-length topic */, payload...}
// after: write a real topic
buf = appendVarInt(buf, len(topic))
buf = append(buf, topic...)
Defensive patterns

Strategy: validation

Validate before calling

func validatePublishTopic(topic string) error {
    if len(topic) == 0 {
        return errors.New("topic cannot be empty")
    }
    return nil
}

Try / catch

if err := client.Publish(topic, payload); err != nil {
    if strings.Contains(err.Error(), "topic cannot be empty") {
        log.Warn("skipping publish: empty topic")
        return
    }
    return err
}

Prevention

When it happens

Trigger: A client sends a PUBLISH packet whose variable header encodes a topic of length 0 (two length bytes 0x00 0x00 and no topic bytes); mqttParsePub then returns errMQTTTopicIsEmpty and the packet is rejected.

Common situations: Hand-rolled or buggy MQTT client serialization that forgets to write the topic field; fuzzing or malformed-packet tests against the server; a truncated/corrupted PUBLISH where the topic bytes were dropped; protocol-level test vectors such as the {"empty topic", []byte{0,0}, 2, ...} case in mqtt_test.go.

Related errors


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/bcc9bfcd454a0fce. Report an issue: GitHub.