MQTT is a lightweight publish and subscribe messaging protocol that runs over TCP with a broker sitting in the middle. You reach for it in an Android app when many devices need to push small updates continuously to many listeners, and live driver location in a delivery or ride hailing app is the case it was made for. It is one option among several, so it belongs next to the wider set of real-time options.
Start with what the protocol actually is.
- Publish and subscribe, never point to point. Clients never address each other. A publisher sends a message to a topic, subscribers register interest in a topic, and the broker does all the routing. Neither side needs to know the other exists.
- Tiny on the wire. The fixed header is two bytes. A position update costs a fraction of what the same update costs as an HTTP request carrying headers and a TLS handshake.
- Built for bad networks and small devices. It was designed in 1999 for satellite links over oil pipelines, so reconnection, session state and delivery guarantees are in the protocol rather than in your app code.
- Two versions in the wild. MQTT 3.1.1 is the OASIS standard everything speaks. MQTT 5 added reason codes on every acknowledgement, session expiry intervals, message expiry, topic aliases, user properties and shared subscriptions. Prefer 5 when the broker supports it.
Then the pieces you should be able to name in the room.
- The broker. One server that accepts connections, stores session and retained state, and fans messages out. It is the single thing you have to run and scale.
- The client id. A per connection identifier the broker uses to find your session again after a reconnect. Two clients sharing one id will keep kicking each other off.
- Topics and wildcards. Topics are slash separated strings like
riders/42/location. Subscribers can use + for exactly one level and # for everything below, and # has to be the last character. Wildcards are for subscribing only, a publish always names an exact topic.
- QoS 0, at most once. One PUBLISH, no acknowledgement, no retry. Cheapest and fastest, and the message can simply be lost.
- QoS 1, at least once. PUBLISH then PUBACK, retried until acknowledged, so duplicates are possible and the receiver has to be idempotent.
- QoS 2, exactly once. A four packet handshake, PUBLISH, PUBREC, PUBREL, PUBCOMP, with state held on both sides. Most expensive and slowest, worth it only for a command you must not run twice.
- QoS is negotiated per leg. The publisher picks one, the subscriber asks for one, and the delivered level is the lower of the two.
- Retained messages. The broker keeps the last message flagged retained on each topic and hands it to a new subscriber the instant it subscribes. Publishing an empty retained payload deletes it.
- Last will and testament. The client hands the broker a message at connect time. The broker publishes it if the connection drops ungracefully, and discards it on a clean disconnect.
- Keep alive and the ping. The client agrees an interval at connect. If it has nothing to send it sends a PINGREQ and gets a PINGRESP back, and the broker declares the client dead after one and a half times the interval.
- Persistent sessions. With
cleanSession false in 3.1.1, or clean start plus a session expiry interval in 5, the broker keeps your subscriptions and queues QoS 1 and 2 messages while you are offline, then delivers them on reconnect.
- MQTT over WebSocket. The same packets wrapped in a WebSocket frame on port 443. It costs a little overhead and gets you through corporate proxies and firewalls that block the native ports.
That set is exactly why delivery and ride hailing apps land on it for live location.
- The broker owns the fan out. Thousands of riders each publish to their own topic, and every customer subscribes to just the one rider they are waiting on. Your servers never have to compute who gets what.
- QoS 0 or 1 for positions. A pin that is replaced a second later is not worth a four packet handshake. QoS 0 for a dense stream, QoS 1 when losing a point would visibly break the trail.
- A retained last position. A customer who opens the app mid trip sees the current pin immediately instead of waiting for the next update.
- A will marks the rider offline. The rider's client sets a will on its status topic, so a dropped phone flips the map to offline on its own without a heartbeat service on your side.
Compared with the alternatives, the tradeoff is narrow but real.
- MQTT. Many to many through a broker you run, with QoS, retained state and wills built in. Best when the fan out pattern is the hard part.
- WebSocket. One socket, bidirectional, no routing or delivery semantics. You build topics, retries and presence yourself, which is fine for one chat screen and painful at rider scale.
- FCM. No connection of your own, and the only option that reaches a killed app. Rate limited and not ordered, so it is a wake up and notification channel, not a location stream.
- Polling. No connection at all, easy to reason about, and wasteful past roughly one request a minute.
On Android specifically, a few implementation points earn credit.
- Client libraries. Paho is the long standing Eclipse client, HiveMQ ships a modern async one. Both handle the packet layer, neither manages your process lifecycle for you.
- Tie the client to a foreground service. Run it inside a foreground service with the
location type while the app is genuinely tracking, and disconnect the moment it is not. Android 14 requires the type declaration and its matching permission.
- Reconnect with exponential backoff and jitter. Mobile networks drop constantly, and a fixed retry from every client turns one broker blip into a thundering herd.
- Keep alive costs battery. Every ping wakes the radio. A longer keep alive saves power and delays how fast the broker notices a dead client, a shorter one detects failure quickly and drains faster, so pick the interval from how stale a rider marker is allowed to be.
- Doze will cut the socket. Once the device is idle with the screen off, network access is suspended and your connection dies. There is no keeping it alive in the background, so FCM is the wake up path and MQTT is the foreground stream.
And security is a fair follow up.
- TLS everywhere. Plain MQTT is cleartext. Use the TLS port and pin or verify the broker certificate the same way you would an HTTPS API.
- Per device credentials. Never one shared username and password in the APK. Issue a short lived token or a client certificate per install, so one leaked device is one revocation.
- Topic ACLs on the broker. A rider may publish only to
riders/<their id>/# and subscribe only to their own orders. Without that, anyone with a valid login can publish a fake position for any rider.
// A generic client interface, so none of this is tied to one library.
interface MqttClient {
suspend fun connect(options: ConnectOptions)
suspend fun subscribe(topic: String, qos: Int, onMessage: (String, ByteArray) -> Unit)
suspend fun publish(topic: String, payload: ByteArray, qos: Int, retained: Boolean)
suspend fun disconnect()
}
suspend fun MqttClient.startTracking(riderId: String) {
connect(
ConnectOptions(
clientId = "rider-$riderId",
keepAliveSeconds = 60, // ping every 60s, declared dead after 90s
cleanStart = false, // keep subscriptions across reconnects
will = Will(
topic = "riders/$riderId/status",
payload = "offline".toByteArray(),
qos = 1,
retained = true, // a late subscriber still sees offline
),
),
)
// Wildcards are for subscribing only. + matches exactly one level.
subscribe("riders/$riderId/orders/+", qos = 1) { topic, payload ->
handleOrder(topic, payload)
}
// QoS 0, because a newer position replaces this one a second later.
publish(
topic = "riders/$riderId/location",
payload = encodePosition(lat, lng),
qos = 0,
retained = true, // the broker holds the current pin
)
}
Answer the shape first, a broker in the middle, topics instead of addresses, and QoS, retained messages and wills as the three features you would not want to rebuild. Then say where you would use it, high frequency many to many updates like live location, with FCM alongside it for the wake up, because that pairing is the answer an interviewer is listening for.