Skip to content
Stand with Ukraine flag

Client type

TBMQ classifies every connecting client as either DEVICE or APPLICATION. This distinction drives message routing, persistence strategy, and resource allocation — ensuring each client type gets the behavior best suited to its role in an IoT deployment.

  • DEVICE clients primarily publish data and subscribe to a limited number of topics with moderate traffic. They represent IoT sensors, actuators, and embedded devices that send telemetry to the broker.

  • APPLICATION clients subscribe to high-rate topics and are expected to process every message reliably, even after an offline period. They represent analytics services, rule engines, or backend processors that consume data from the broker.

For example, a temperature sensor publishing readings every second is a DEVICE client, while a cloud analytics service subscribing to data from thousands of sensors is an APPLICATION client.

TBMQ resolves the client type during MQTT CONNECT packet processing, based on the credentials used:

  • When the Basic, X.509, JWT, and HTTP providers are all disabled, every client is accepted without authentication and assigned the DEVICE type. SCRAM does not take part in this check, because it runs on the separate MQTT 5 enhanced authentication path.
  • When any provider is enabled, TBMQ takes the client type from whichever provider authenticated the client:
    • Basic, X.509 Certificate Chain, and SCRAM — the clientType field of the matched credentials.
    • JWT — resolved from the token claims: if at least one client type claim is configured and every one of them matches, the type is the opposite of the provider’s default client type. In every other case, including when no client type claims are configured at all, the default is used.
    • HTTP — the clientType field of the response returned by the external authentication service, falling back to the provider’s default client type when that field is missing, empty, or not a recognized type.

The client type of new credentials defaults to DEVICE, and the UI offers only DEVICE and APPLICATION.

For details on client authentication, see the Security overview. For instructions on creating credentials with a specific client type, see the MQTT client credentials guide. Changing the client type on existing credentials does not affect a connected session — the new type applies from the client’s next CONNECT.

All published messages are written to the tbmq.msg.all Kafka topic. How messages are forwarded from there depends on the client type and whether the client uses a persistent session.

A Kafka consumer polls the tbmq.msg.all topic and forwards messages to their subscribers:

  • Non-persistent clients — messages are delivered directly to the connected client. No state is retained. See Non-persistent client in the architecture overview.
  • Persistent clients — the session survives disconnections. Messages published while the client is offline are stored and delivered on reconnect. The handling differs by client type (see sections below). See Persistent client in the architecture overview.

There is one exception: if a persistent subscriber uses QoS 0, or if the publisher sends at QoS 0, messages are delivered without additional persistence steps. This is a result of QoS downgrading.

DEVICE clients can be persistent or non-persistent, depending on the CONNECT packet settings.

For persistent DEVICE clients, matching messages are forwarded to the tbmq.msg.persisted Kafka topic. A dedicated Kafka consumer reads this topic and persists the messages in Redis before delivering them to online subscribers. When an offline DEVICE client reconnects, it receives the messages stored in Redis.

Redis holds one queue per client, bounded by both a message count and a TTL:

persistent-session:
device:
persisted-messages:
# Maximum number of PUBLISH messages stored for each persisted DEVICE client.
limit: "${MQTT_PERSISTENT_SESSION_DEVICE_PERSISTED_MESSAGES_LIMIT:10000}"
# TTL of persisted DEVICE messages (in s). Defaults to one week.
ttl: "${MQTT_PERSISTENT_SESSION_DEVICE_PERSISTED_MESSAGES_TTL:604800}"
  • When a client’s queue exceeds limit, TBMQ trims it by removing the oldest messages first, so a client that stays offline for a long time keeps the most recent messages and loses the earliest ones.
  • Each stored message expires independently after ttl seconds. For MQTT 5 publishers, the message’s Message Expiry Interval property overrides this default for that message.

If an APPLICATION client connects with a non-persistent session, messages are still delivered while connected, but no persistence mechanism is applied — messages can be lost if the client disconnects.

TBMQ displays an Application client should be persistent warning on the session details page when it detects this configuration:

For persistent APPLICATION clients, TBMQ creates a dedicated Kafka topic per client:

tbmq.msg.app.$CLIENT_ID

TBMQ creates the topic when the client connects as APPLICATION type with a persistent session and the broker starts processing its persisted messages. A dedicated Kafka consumer per client polls this topic and delivers messages to the client in its own thread, providing isolated, high-throughput delivery. Because every connected persistent APPLICATION client costs a Kafka topic, a Kafka consumer, and a thread, this type is meant for a limited number of backend consumers rather than for devices.

MQTT_APPLICATION_CLIENTS_LIMIT caps how many persistent APPLICATION clients and integrations the cluster may hold in total — the count is shared across nodes, not per node. It defaults to 0, which disables the limit. Once the limit is reached, TBMQ refuses further CONNECT requests: MQTT 5 clients get CONNACK reason code 0x97 Quota exceeded, MQTT 3.x clients get 0x03 Server unavailable.

Client IDs with special characters — MQTT allows characters in a client ID that Kafka does not accept in a topic name. TBMQ therefore requires the client ID to be purely alphanumeric to use it verbatim, which is stricter than Kafka’s own [a-zA-Z0-9._-]: even a Kafka-legal ID such as my-app.1 is replaced by its SHA-256 hash:

tbmq.msg.app.$CLIENT_ID_HASH

This substitution is controlled by the TB_APP_PERSISTED_MSG_CLIENT_ID_VALIDATION environment variable (true by default). Setting it to false makes TBMQ use the client ID verbatim as the topic name — only safe when every APPLICATION client ID is already a valid Kafka topic name, otherwise topic creation fails for that client.

APPLICATION shared subscriptions get their own topic per topic filter, named tbmq.msg.app.shared.$TOPIC_FILTER with / replaced by . and the + / # wildcards replaced by slw / mlw. The equivalent hashing fallback is controlled by TB_APP_PERSISTED_MSG_SHARED_TOPIC_VALIDATION, and retention by TB_KAFKA_APP_PERSISTED_MSG_SHARED_TOPIC_PROPERTIES.

Feature DEVICE client APPLICATION client
Typical role Publishes telemetry; moderate subscriptions Subscribes to high-rate topics; processes data
Persistence Persistent or non-persistent Expected to be persistent
Default type (no auth) Yes No
Storage for non-persistent sessions No persistence No persistence
Storage for persistent sessions Redis Dedicated Kafka topic per client
Offline message delivery Yes (if persistent), via Redis Yes (if persistent), via Kafka
Common use cases IoT sensors, actuators, and embedded devices Analytics systems, rule engines, and data processors