MQTT 3.1.1 vs 5.0 es la pregunta de versión que se plantea todo equipo cuando un proyecto deja de ser un prototipo: ¿merece la pena migrar al protocolo más reciente y pueden convivir dispositivos antiguos y nuevos? Esta guía compara cómo se comportan ambas versiones en la red y en la práctica, indica qué librerías cliente populares admiten MQTT 5 y te ofrece una checklist para migrar una flota mixta grupo a grupo.
Un poco de historia
MQTT 3.1.1 se convirtió en estándar de OASIS en 2014 y sigue siendo la versión más desplegada. Es pequeña, estable y compatible con prácticamente todas las librerías cliente y todos los dispositivos. MQTT 5.0 llegó como estándar de OASIS en 2019. Mantuvo el mismo modelo central, topics, niveles de QoS, retained messages y Last Will, y añadió las funciones que los grandes despliegues venían construyendo a mano: errores más claros, expiración, control de flujo, metadatos y balanceo de carga. No hubo versión 4; el byte de nivel de protocolo es 4 para la 3.1.1 y 5 para la 5.0, por eso las librerías suelen llamar a la 3.1.1 "protocol version 4".
La buena noticia para la migración es que un broker puede servir ambas versiones a la vez. Cada cliente declara su versión en el paquete CONNECT, y los mensajes fluyen entre clientes 3.1.1 y 5.0 en los mismos topics.
MQTT 3.1.1 vs 5.0 lado a lado
| Comportamiento | MQTT 3.1.1 | MQTT 5.0 |
|---|---|---|
| Notificación de errores | Códigos de retorno de CONNACK del 0 al 5; la mayoría de los demás fallos simplemente cierran la conexión | Reason codes en CONNACK, PUBACK, SUBACK, UNSUBACK, DISCONNECT y más, con reason strings opcionales |
| Duración de la sesión | Flag clean session: para siempre o nunca | Clean start más session expiry interval en segundos |
| Duración del mensaje | Los mensajes esperan indefinidamente en las sesiones | Message expiry interval por mensaje |
| Consumidores con balanceo de carga | No estándar | Suscripciones compartidas con $share/group/filter |
| Sobrecarga del topic | Topic completo en cada PUBLISH | El topic alias sustituye el topic por un entero |
| Metadatos | Solo en el payload | User properties, content type, payload format indicator |
| Petición/respuesta | Solo por convención | Response topic y correlation data |
| Control de flujo | Nada estándar | Receive maximum, maximum packet size |
| Desconexión por el servidor | El servidor cierra el socket en silencio | El servidor envía DISCONNECT con un reason code |
| Opciones de suscripción | Solo QoS | No local, retain as published, retain handling, subscription identifiers |
| Mensaje will | Se envía de inmediato tras una desconexión abrupta | Will delay interval opcional |
| Autenticación | Usuario y contraseña | Además, autenticación mejorada mediante el paquete AUTH |
| Capacidades del servidor | Implícitas | El servidor puede anunciar QoS máxima, disponibilidad de retain, keep alive impuesto y client ID asignado |
Los cambios que más importan en la práctica
Reason codes. En 3.1.1, si un broker rechaza una publicación por una cuota o un permiso, el cliente a menudo solo ve cómo se cae la conexión y tiene que adivinar el motivo. En MQTT 5 el broker puede decir por qué, por ejemplo 0x97 (cuota superada) o 0x87 (no autorizado). Solo eso ya acorta muchísimo la depuración. La referencia de reason codes de MQTT los recoge todos.
Expiración de sesiones y mensajes. Con 3.1.1, una sesión persistente vive hasta que el cliente vuelve, lo que puede suponer un broker guardando mensajes para dispositivos que se dieron de baja hace meses. MQTT 5 permite al cliente decir "mantén mi sesión 10 minutos" y a los publicadores decir "este comando no sirve de nada pasados 30 segundos". Consulta session expiry interval en MQTT para ver consejos de ajuste.
Suscripciones compartidas. Varias instancias de un servicio backend pueden suscribirse a $share/workers/orders/+/new y el broker reparte los mensajes entre ellas. Antes de MQTT 5 esto requería extensiones específicas del broker o una cola aparte.
User properties y petición/respuesta. Metadatos como una versión de esquema o un ID de traza pueden viajar en propiedades en lugar de envolverse en cada payload, y los flujos de petición/respuesta disponen de un response topic y una correlation data estándar.
Para un recorrido función por función con ejemplos, lee las funciones de MQTT 5.
Lo que no cambia
La sintaxis de los topics y los comodines, los tres niveles de QoS, los retained messages, el Last Will, el keep alive, los puertos y TLS funcionan igual. Los payloads siguen siendo bytes opacos. Un suscriptor 3.1.1 recibe los mensajes publicados por un cliente 5.0; simplemente no ve las propiedades de MQTT 5. Esa compatibilidad es lo que hace posible una actualización gradual.
Soporte en las librerías cliente
| Plataforma | Librería | Soporte de MQTT 5 |
|---|---|---|
| Python | paho-mqtt 2.x | Sí, pasa protocol=mqtt.MQTTv5 |
| Node.js y navegadores | MQTT.js v5 | Sí, configura protocolVersion: 5 (el valor por defecto es 4) |
| Go | Eclipse Paho paho.mqtt.golang |
No, solo 3.1.1 |
| Go | Eclipse Paho paho.golang |
Sí, solo MQTT 5 |
| Java | Eclipse Paho Java (paquete mqttv5), HiveMQ MQTT Client |
Sí |
| .NET | MQTTnet | Sí |
| ESP32 (ESP-IDF) | esp-mqtt | Sí, cuando el soporte de MQTT 5 está habilitado en la configuración de compilación |
| Arduino | PubSubClient, AsyncMqttClient | No, solo 3.1.1 |
| CLI | mosquitto_pub y mosquitto_sub | Sí, con -V mqttv5 |
Las versiones de las librerías cambian, así que confírmalo en la documentación oficial antes de comprometerte. El patrón a destacar: las librerías de servidor y de gateway suelen admitir MQTT 5, mientras que muchas librerías populares para microcontroladores siguen siendo solo 3.1.1. Ese es el motivo principal de que las flotas acaben siendo mixtas.
Usar las funciones de MQTT 5 en el código
Este ejemplo en Python se conecta con MQTT 5, solicita una sesión de 10 minutos, publica un comando con una expiración de 30 segundos y user properties, e imprime los reason codes. Funciona contra el servidor MQTT público gratuito, que admite ambas versiones y una expiración de sesión de hasta 30 minutos.
import time
import uuid
import paho.mqtt.client as mqtt
from paho.mqtt.packettypes import PacketTypes
from paho.mqtt.properties import Properties
PREFIX = f"connectmq/demo/{uuid.uuid4().hex[:8]}"
def on_connect(client, userdata, flags, reason_code, properties):
print("CONNACK:", reason_code, "| session present:", flags.session_present)
client.subscribe(f"{PREFIX}/cmd/#", qos=1)
def on_subscribe(client, userdata, mid, reason_code_list, properties):
print("SUBACK:", [str(rc) for rc in reason_code_list])
def on_message(client, userdata, msg):
props = msg.properties
user_props = getattr(props, "UserProperty", None)
expiry = getattr(props, "MessageExpiryInterval", None)
print(f"{msg.topic} {msg.payload.decode()} user={user_props} expiry={expiry}")
def on_publish(client, userdata, mid, reason_code, properties):
print("PUBACK:", reason_code)
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
client_id=f"v5-demo-{uuid.uuid4().hex[:8]}",
protocol=mqtt.MQTTv5)
client.on_connect = on_connect
client.on_subscribe = on_subscribe
client.on_message = on_message
client.on_publish = on_publish
connect_props = Properties(PacketTypes.CONNECT)
connect_props.SessionExpiryInterval = 600 # mantiene la sesión 10 minutos
client.connect("broker.connectmq.com", 1883, keepalive=30,
clean_start=False, properties=connect_props)
client.loop_start()
time.sleep(1)
pub_props = Properties(PacketTypes.PUBLISH)
pub_props.MessageExpiryInterval = 30
pub_props.UserProperty = [("schema", "v2"), ("source", "ops-console")]
pub_props.ContentType = "application/json"
client.publish(f"{PREFIX}/cmd/pump7", '{"action":"stop"}', qos=1,
properties=pub_props)
time.sleep(2)
client.loop_stop()
client.disconnect()
Para ver la interoperabilidad, ejecuta un suscriptor 3.1.1 en el mismo topic desde otra terminal. Recibe el payload, pero no las propiedades.
mosquitto_sub -h broker.connectmq.com -p 1883 -V mqttv311 \
-i v311-check-$RANDOM -t "connectmq/demo/+/cmd/#" -v
Checklist de migración
Usa esta checklist para pasar de 3.1.1 a 5.0 sin un cambio brusco de una sola vez.
- Confirma el soporte del broker. Tu broker debe aceptar ambas versiones simultáneamente. ConnectMQ lo hace, así que clientes antiguos y nuevos pueden compartir topics durante la transición.
- Inventaría los clientes por librería. Enumera cada tipo de dispositivo, gateway y servicio con su librería MQTT y su versión. Marca cuáles pueden pasar a MQTT 5 hoy mismo.
- Actualiza primero los servicios backend. Son los que más ganan (reason codes, suscripciones compartidas, control de flujo) y los más fáciles de volver a desplegar.
- Sustituye la lógica de clean session. Convierte
clean_session=Trueen clean start con session expiry 0, yclean_session=Falseen clean start false con una expiración explícita en lugar de "para siempre". - Gestiona los reason codes. Registra los reason codes y reason strings de CONNACK, SUBACK, PUBACK y DISCONNECT, y deja de reintentar ante códigos no reintentables como "no autorizado".
- No dependas todavía de las propiedades para datos críticos. Mientras queden suscriptores 3.1.1, todo lo que necesiten debe seguir en el payload o en el topic.
- Introduce la expiración de forma deliberada. Añade primero la expiración de mensajes a los comandos y a las alertas sensibles al tiempo, donde una entrega obsoleta es perjudicial.
- Adopta suscripciones compartidas para los consumidores escalados horizontalmente y elimina cualquier deduplicación casera entre instancias.
- Actualiza los gateways y dispositivos al final, por grupos, vigilando el estado de las conexiones y las tasas de error tras cada oleada.
- Vuelve a probar lo básico. La reconexión, el Last Will, el estado retenido y las sesiones persistentes deben comportarse como antes; la checklist de pruebas de MQTT cubre cada punto.
¿Deberías actualizar?
En proyectos nuevos, empieza con MQTT 5 siempre que tus librerías lo admitan. En flotas existentes, actualiza ya los servicios y gateways, y los dispositivos cuando surja la ocasión, por ejemplo en su próximo ciclo de firmware. Hay pocas razones para apresurarse con dispositivos limitados cuya librería solo admite 3.1.1, porque un broker que habla ambas versiones elimina la presión. El tutorial de MQTT cubre los fundamentos comunes si estás incorporando a nuevos miembros al equipo.
Un broker MQTT gestionado que admita ambas versiones hace que la transición transcurra sin sobresaltos. ConnectMQ acepta clientes MQTT 3.1.1 y 5.0 en el mismo broker y admite funciones de MQTT 5 como session expiry, message expiry, topic alias, response topic, retained messages y reason codes, con suscripciones compartidas en el plan Serverless (Paid).
Actualiza en ConnectMQ a tu propio ritmo
Crea un broker Serverless gratuito en app.connectmq.com, sin tarjeta de crédito, conecta un dispositivo 3.1.1 y un servicio MQTT 5, y confirma que interoperan antes de planificar el resto de la migración.
Preguntas frecuentes
¿Son compatibles MQTT 3.1.1 y MQTT 5.0?
En el mismo broker, sí. Los clientes eligen su versión al conectarse y los mensajes fluyen entre versiones. Un suscriptor 3.1.1 simplemente no recibe las propiedades de MQTT 5.
¿Cuál es la mayor ventaja de MQTT 5?
Para la mayoría de los equipos son los reason codes, porque los fallos dejan de ser desconexiones silenciosas. La expiración de sesiones y mensajes y las suscripciones compartidas van muy de cerca.
¿Por qué a MQTT 3.1.1 se le llama protocol version 4?
El paquete CONNECT incluye un byte de nivel de protocolo, que es 4 para MQTT 3.1.1 y 5 para MQTT 5.0. Librerías como MQTT.js usan ese número en sus opciones.
¿Tengo que actualizar mis dispositivos ESP32 a MQTT 5?
No necesariamente. Si usas una librería 3.1.1 como PubSubClient, los dispositivos pueden seguir funcionando contra un broker que admita ambas versiones mientras tu backend adopta MQTT 5.



