MQTT and MongoDB: From Live Sensor Data to Reliable Storage
Many applications that work with sensors, machines, or other connected devices need the same MQTT data in more than one place.
A dashboard may need to display readings immediately as they arrive. Another application may simply want to ask an HTTP API for the most recent value. At the same time, the system may need to preserve every incoming message in MongoDB so that the information can be queried later.
At first glance, this looks like a simple integration problem.
A service connects to an MQTT broker, subscribes to the required topics, receives messages, stores them in MongoDB, and perhaps exposes an HTTP endpoint for clients.
The difficulty begins when the system has to satisfy all of those requirements at once.
A live dashboard and a persistent database have fundamentally different expectations.
A browser receiving a live stream generally has little use for an old backlog. If the browser falls behind by several hundred messages, replaying everything may be less useful than simply delivering the newest available value.
A database writer has exactly the opposite requirement. Missing one message can create a permanent gap in the stored history. A temporary database failure therefore cannot simply be treated as an acceptable loss.
This difference in delivery requirements became one of the central design considerations behind the MQTT module for RESTHeart.
The module provides a common MQTT connection and routing layer while allowing different consumers to use different delivery policies. Live consumers can prioritize current data and tolerate drops, while durable consumers can apply backpressure, retry failed writes, and preserve messages that cannot immediately be stored.
The result is an architecture in which the same MQTT connection can support real-time clients and persistent storage without forcing both use cases to behave identically.
Why MQTT and MongoDB Need More Than a Simple Connector
MQTT is well suited to applications where devices publish relatively small messages and other systems subscribe to them.
A sensor might publish temperature readings under a topic such as:
sensors/room1/temperature
Another device could publish humidity under:
sensors/room1/humidity
An application can subscribe to a topic filter and receive matching messages as they are published.
MongoDB, meanwhile, provides a persistent document database that can hold the received payload and associated MQTT information.
Combining the two is therefore natural.
The basic pipeline looks simple:
MQTT broker → MQTT consumer → MongoDB
But production behavior depends on what happens between those three components.
What if MongoDB is temporarily unavailable?
What if the process crashes immediately after receiving an MQTT message?
What if the broker reconnects and redelivers a message?
What if two subscriptions match the same publication?
What if a browser cannot process messages as quickly as the broker delivers them?
What if a user is authenticated to RESTHeart but should not be allowed to subscribe to a particular MQTT topic?
Those questions turn a simple connector into a message-delivery problem.
The RESTHeart MQTT module addresses these situations by separating the requirements of live consumers from those of durable consumers.
One MQTT Connection, Different Consumer Behaviors
A key part of the architecture is that different listeners do not have to use the same delivery policy.
A plugin can register a live listener through subscribe, or it can register a durable listener through subscribeDurable.
These two types of consumers have different purposes.
Live consumers
A live consumer is useful when the client primarily cares about what is happening now.
For example, an SSE connection may feed a browser dashboard. The dashboard wants the current sensor state rather than an enormous backlog of old readings.
If the client falls behind, the system can discard messages instead of allowing an unlimited queue to accumulate.
The live path therefore includes mechanisms such as:
- A global rate limit for live listeners
- A bounded queue for each SSE connection
- An optional throttle stage
- Counters for dropped messages
The important point is that the system does not silently treat dropped messages as if they had been delivered successfully. Drops are counted so that the behavior can be observed.
Durable consumers
The MongoDB writer has a different requirement.
For persistent storage, losing a message means losing part of the historical data.
If a sensor sends readings every second and one reading disappears during a database outage, the missing value may only become apparent much later when somebody queries that period.
For this reason, the durable path uses mechanisms designed to avoid unnecessary loss:
- Backpressure
- Retries
- A dead-letter file
- Broker redelivery through the MQTT session
- Acknowledgement only after successful storage or dead-letter handling
This separation is important because protecting a live browser connection and protecting historical data are not the same problem.
A policy that makes sense for one consumer can be harmful to another.
Why the First Design Could Lose MQTT Messages
The first implementation exposed a fundamental problem with acknowledgement timing.
The MQTT client acknowledged a message immediately after receiving it from the broker.
That sounds efficient, but it changes the delivery guarantee.
Once the message has been acknowledged, the broker no longer needs to redeliver it. If the application crashes before the message is actually stored, the message has already been considered delivered.
For example, imagine this sequence:
- The broker sends a sensor reading.
- The application receives it.
- The application acknowledges it.
- The process crashes.
- MongoDB never receives the reading.
The broker has no reason to send the message again because it already received the acknowledgement.
The message is therefore lost.
This behavior effectively made the module at-most-once from the perspective of persistent storage, regardless of the MQTT QoS configuration.
The problem was not simply the broker’s delivery mechanism. The problem was that the application acknowledged the message before completing the operation that actually mattered.
Acknowledgement Comes After Storage
The current design changes the order of those operations.
For the durable MongoDB path, a message is acknowledged only after one of two things has happened:
- The message has been successfully stored in MongoDB.
- The message has been written to the dead-letter file.
That distinction is important.
The broker acknowledgement now represents completion of the durable consumer’s responsibility rather than merely receipt by the MQTT client.
With MQTT QoS 1 or QoS 2, clean-session: false, and a stable client ID, an MQTT session can retain messages that were not successfully completed.
If the connection disappears before the message is acknowledged, the broker can redeliver the outstanding message when the session resumes.
The client ID is therefore significant.
In this implementation, the default client ID is derived from the RESTHeart instance name. Because that value remains consistent across restarts, the MQTT session can be resumed instead of being treated as a completely new client.
The overall sequence becomes:
Broker → RESTHeart → MongoDB/dead-letter → acknowledgement
rather than:
Broker → acknowledgement → MongoDB
That ordering is what allows broker redelivery to remain useful when the application fails during processing.
Startup Ordering Can Affect Redelivery
Another subtle issue appears during startup.
It is not enough to establish the MQTT connection eventually. The order in which components become ready matters.
A broker can deliver messages owed by a resumed MQTT session as soon as the connection is accepted.
If the MQTT connection becomes active before all of the intended consumers have registered, a message may arrive before the relevant consumer exists.
That creates a race during startup.
To avoid this, the architecture includes a dedicated mqtt-connector component.
The connector opens the broker connection only after all consumers have been registered, including the MongoDB writer.
This means that when the broker begins delivering messages, the routing system is already prepared to process them.
The distinction is small in code but important in behavior.
The connection is not treated as an isolated networking operation. It is part of the lifecycle of the complete consumer system.
At-Least-Once Delivery Introduces a Different Problem
Avoiding lost messages does not mean every message will appear exactly once.
At-least-once delivery deliberately favors another trade-off:
A message may be delivered more than once, but an unacknowledged message should not simply disappear.
This matters when a process crashes after writing a message but before acknowledging it.
Consider this sequence:
- MQTT sends a message.
- The writer stores it in MongoDB.
- The process crashes before the MQTT acknowledgement is completed.
- The broker considers the message outstanding.
- The connection resumes.
- The broker sends the message again.
- MongoDB receives the same logical reading twice.
The system has avoided a loss, but it now has a duplicate.
This is a normal consequence of at-least-once delivery.
The application therefore needs a strategy for deciding what constitutes the identity of a stored message.
Message Identity and Duplicate Handling
The MQTT module provides different storage strategies for this purpose.
The default auto strategy stores each delivery as a new MongoDB document.
This is the simplest behavior. Every received delivery becomes a separate document.
That can be appropriate when every delivery is intentionally treated as an individual event.
However, it does not eliminate duplicates caused by redelivery.
The alternative payload-field strategy uses an identifier contained in the message payload.
If the publisher supplies a stable identifier, the writer can use that value during an upsert.
Instead of creating another document when the same message is delivered again, the existing document can be updated.
This makes the publisher-provided identifier important.
A stable message identity cannot safely be reconstructed from information that changes between deliveries.
Why Reception Time Is Not a Reliable Message Identity
An earlier approach attempted to create a unique identity by hashing the MQTT topic together with the time at which the message was received.
That sounds reasonable until redelivery is considered.
The first delivery has one reception timestamp.
A later redelivery occurs at another time.
The resulting hash is therefore different even though the underlying MQTT message is the same.
The database writer cannot recognize that the second delivery represents the same logical message.
The apparent deduplication strategy therefore did not actually provide reliable deduplication.
That strategy was removed.
The lesson is broader than this specific implementation:
If an application needs stable message identity, the identity must be based on information that remains stable across deliveries.
In this design, that stable identity can come from the publisher through a field in the payload.
Multiple RESTHeart Nodes and Duplicate Delivery
The identity issue also matters when several RESTHeart nodes consume MQTT messages.
With MQTT 3.1.1, multiple clients subscribing to a matching topic can each receive the publication.
If several RESTHeart instances receive the same MQTT message, the MongoDB writer on each node can potentially process that message.
Again, a stable identifier from the publisher can allow the database operation to converge on the same document through an upsert.
Without a stable identifier, the default behavior is to store each delivery as a separate document.
This is why duplicate handling cannot be considered independently from the message format.
The storage layer can only deduplicate reliably when it has a reliable way to identify what should count as the same message.
A Slow MongoDB Must Not Freeze Live MQTT Consumers
Another architectural problem appeared when the database writer became slow.
The writer uses a buffer and applies backpressure when that buffer is full.
Backpressure can be useful because it prevents unlimited memory growth. Instead of accepting an unlimited number of messages into memory, the system can slow down when downstream processing cannot keep up.
But an unlimited wait introduces another problem.
Suppose MongoDB stops responding.
Messages begin accumulating in the writer’s buffer.
Eventually, the buffer becomes full.
If incoming messages are allowed to wait forever for buffer space, those messages remain unacknowledged.
As enough messages remain outstanding, the MQTT broker’s in-flight window can become full.
At that point, the broker can stop delivering additional messages to the client.
That affects more than MongoDB.
The same MQTT connection is also supporting live consumers.
A database outage can therefore become a dashboard outage.
The problem is not that backpressure is inherently wrong. The problem is that an unbounded wait can create an unexpected dependency between consumers.
Bounding the Buffer Wait
The current implementation puts a limit on how long a message can wait for space in the buffer.
The setting is:
buffer.max-wait-ms
Its default value is 30 seconds.
If the buffer becomes full, an incoming message can wait for available space.
If space becomes available within the configured period, processing can continue.
If the wait exceeds the configured limit, the message is dropped and counted in:
mqtt_buffer_dropped
The message is then acknowledged.
This is a deliberate trade-off.
The buffer can absorb a short MongoDB interruption, such as a restart lasting a few seconds.
But the system does not pretend that an arbitrarily long database outage can be solved by continuously increasing an in-memory queue.
A longer outage requires an appropriately designed MongoDB deployment.
Simply making the memory buffer larger would only postpone the point at which messages must eventually be discarded.
Why an Unlimited Queue Is Not a Complete Solution
It can be tempting to respond to a slow database by increasing the queue size.
For example, if the application normally receives 100 messages per second and MongoDB temporarily stops accepting writes, a larger queue gives the database more time to recover.
But queue capacity does not remove the underlying problem.
If the outage continues long enough, any finite queue eventually fills.
An unlimited queue creates a different risk: memory usage can continue growing until the application itself becomes unstable.
The bounded waiting strategy makes the trade-off explicit.
Short interruptions can be absorbed.
Longer interruptions are counted as losses rather than being allowed to block the complete MQTT connection indefinitely.
For operators who deliberately want the previous coupling, setting buffer.max-wait-ms to 0 restores the unbounded wait behavior.
That option exists for deployments where the operator accepts the consequences.
MQTT Payloads Are Binary Data
Another important detail is the MQTT payload itself.
MQTT messages carry bytes.
Applications often treat those bytes as text, but MQTT does not require every payload to be textual.
A message may contain ordinary text, encoded data, or binary content.
The HTTP services therefore expose whether a payload is represented as text or base64.
MongoDB likewise stores textual payloads as strings and binary payloads as binary data.
This avoids silently assuming that every MQTT message is UTF-8 text.
The distinction becomes especially important when the same infrastructure is used by different types of devices.
A sensor might publish a simple textual value, while another device could publish binary information.
The storage representation needs to preserve that distinction.
MQTT 5 Publish Properties
MQTT 5 adds publish properties that can contain additional information associated with a message.
The module preserves those properties.
One detail is particularly important for user properties.
MQTT 5 allows repeated property names.
For that reason, user properties are represented as an ordered list rather than as a simple object where each property name can occur only once.
This preserves the structure of the MQTT message more accurately.
It also avoids silently discarding repeated properties simply because the storage representation was designed like an ordinary key-value object.
The general principle is straightforward:
When adapting a protocol to another representation, information should not be removed merely because the target representation looks simpler.
Retained Messages and Reception Time
MQTT retained messages provide another example where seemingly simple fields can have important meanings.
When a broker sends a retained message to a new subscription, the retain flag indicates that the message was retained.
The value may have originally been published much earlier.
However, when the consumer receives it, the application has a new reception event.
Suppose a temperature value was published days ago and retained by the broker.
A new subscriber connects today.
The broker sends the retained value to the subscriber.
The value itself may be old, but the application’s reception time is current.
That distinction matters when data is stored in MongoDB.
The system therefore keeps the relevant MQTT information instead of treating the reception time as if it represented the original publication time.
MQTT Message Expiry
MQTT message expiry also requires careful handling.
The broker can reduce the remaining expiry interval before delivering the message.
For example, a message might have an expiry interval of 120 seconds when it is originally published.
If the broker holds it for a period before delivery, the subscriber can receive a smaller remaining value.
In the described testing scenario, a value of 120 seconds at publication was received as 99 seconds after approximately 20 seconds.
The writer stores the value as received and calculates an expiresAt date from the remaining interval.
This means the database reflects the expiry information available to the consumer rather than assuming that the original configured interval is still entirely available.
MQTT Subscription Overlap Can Create Extra Messages
Subscription design is another area where a small configuration difference can have a visible effect.
Consider these two subscriptions:
sensors/#
and:
sensors/temp
A message published to sensors/temp matches both filters.
If both subscriptions are independently registered with the broker, Mosquitto delivers a copy for each matching subscription.
That can lead to unexpected duplicate processing.
For example, suppose the router subscribes to:
sensors/#
while an SSE client also requests:
sensors/temp.
A temperature message can then arrive through both matching subscriptions.
If both copies reach the MongoDB writer, two documents can be created for what was logically one publication.
This is not necessarily a database bug.
It is a consequence of how the subscriptions were configured.
The Router Reduces Redundant Broker Subscriptions
The module addresses this by checking the registered filters.
The router subscribes to the broker only when a registered filter is not already covered by another filter.
In other words, the system tries to avoid creating redundant broker subscriptions when one existing subscription already encompasses another requested filter.
This is particularly useful when different application consumers request overlapping topic ranges.
The routing layer therefore does more than simply forward messages.
It also manages the relationship between application-level listeners and the broker-level subscription set.
Authentication and MQTT Topic Permissions
Connecting MQTT to an HTTP platform introduces another security question.
A user who successfully authenticates to RESTHeart is not automatically entitled to read every MQTT topic.
Authentication answers one question:
Who is the user?
Authorization answers another:
What is that user allowed to access?
The MQTT module therefore performs topic authorization before allowing the requested MQTT service to proceed.
The requested topic filter is checked against the account’s ACL.
If no rule permits the requested filter, access is denied.
This means MQTT access can follow the authorization model of the surrounding RESTHeart environment instead of treating broker subscriptions as completely separate from HTTP authentication.
Topic Filter Containment
The authorization logic uses topic-filter containment.
This distinction matters because MQTT wildcards have different scopes.
For example:
sensors/+
can match:
sensors/room1
but it does not cover:
sensors/room1/temperature
The # wildcard has broader matching behavior.
A permission for:
sensors/+
therefore should not automatically be interpreted as permission for:
sensors/#.
This is why authorization cannot simply compare strings for equality or assume that one wildcard expression automatically grants another.
The authorizer needs to determine whether the permitted filter actually covers the requested filter.
If there is no matching authorization rule, the request is rejected before it reaches the MQTT service.
Secure Defaults for the HTTP Endpoints
The MQTT authorization layer is enabled by default.
The HTTP endpoints, however, remain disabled until explicitly configured.
This provides an important operational distinction.
An operator does not automatically expose MQTT-related HTTP functionality merely by installing or loading the module.
The first endpoint that is enabled is already subject to the authorization mechanism.
That makes the authorization check part of the service rather than something that has to be manually added afterward.
SSE for Live MQTT Data
Server-Sent Events are useful for the live side of the architecture because a browser can maintain an HTTP connection and receive events as data becomes available.
For a dashboard, this can provide a straightforward path:
MQTT broker → RESTHeart MQTT module → SSE → browser
The browser does not need to establish its own MQTT connection.
This has another architectural benefit.
The browser does not need its own MQTT credentials, broker connection, subscription management, or payload-decoding logic.
Instead, RESTHeart remains responsible for the broker connection and authorization, while the browser consumes the HTTP/SSE interface.
This keeps the live client separate from the broker infrastructure.
Why Live Data Can Be Allowed to Drop
Dropping data sounds undesirable until the purpose of the consumer is considered.
Imagine a dashboard that displays the current temperature.
If the sensor produces readings faster than the browser can render them, delivering every historical intermediate value may not improve the dashboard.
Suppose the browser falls behind by several hundred readings.
Sending all of those readings could make the browser process outdated information while the actual current temperature continues changing.
For this type of consumer, keeping the stream responsive can be more useful than guaranteeing delivery of every intermediate event.
That is why live listeners use bounded queues and can drop messages when necessary.
The dropped messages are counted rather than silently ignored.
Why MongoDB Has the Opposite Requirement
The MongoDB writer is not showing a transient view of the current state.
It is building a historical record.
If one message disappears, a query for that period can expose a gap.
That makes loss much more significant.
The writer therefore uses durable processing behavior, including waiting, retries, and dead-letter handling.
The difference can be summarized like this:
| Consumer | Main Goal | Delivery Behavior |
|---|---|---|
| SSE dashboard | Current information | Can drop outdated messages |
| MongoDB writer | Historical persistence | Attempts to preserve messages |
| HTTP latest-value client | Current stored state | Reads persisted data |
| MQTT broker | Message transport | Supports configured MQTT delivery semantics |
The architecture works because these consumers are not forced into one delivery model.
Retries and the Dead-Letter Path
A temporary MongoDB failure does not necessarily mean the message should immediately be discarded.
The durable writer can retry failed storage operations.
If a message still cannot be stored and the buffering rules require the system to move forward, the message can be written to a dead-letter file.
Once the message has been placed in that dead-letter path, it can be acknowledged to the broker.
This creates a clear boundary.
The message has either reached MongoDB or reached the durable fallback represented by the dead-letter file.
That is preferable to acknowledging a message simply because it entered the application process.
The dead-letter mechanism also provides an operational record of messages that could not follow the normal storage path.
The Complete Flow
Putting the pieces together, the architecture can be viewed as a set of stages.
First, RESTHeart initializes the MQTT consumers.
The MongoDB writer and other listeners are registered before the MQTT connection is opened.
The dedicated connector then establishes the broker connection.
The router manages subscriptions and routes incoming MQTT messages to the appropriate listeners.
Live consumers receive data through their bounded processing paths.
The MongoDB writer processes messages through its durable path.
For a successfully stored message, the writer completes the database operation before the broker acknowledgement is issued.
If storage cannot be completed but the message is successfully written to the dead-letter file, the message can also be acknowledged.
If the application disappears before acknowledgement, MQTT session behavior can allow the broker to redeliver the outstanding message when the session resumes, assuming the relevant QoS, session configuration, and stable client identity are in place.
This creates a more reliable boundary between MQTT delivery and MongoDB persistence.
The Architecture Is About Different Failure Policies
The most important idea behind this MQTT and MongoDB integration is not simply that RESTHeart can connect to an MQTT broker.
The more significant design decision is that different consumers are allowed to respond differently to failure.
A live dashboard is generally interested in current information.
A persistent writer is interested in completeness.
Treating both consumers identically creates problems.
If everything uses strict backpressure, a slow database can interfere with live clients.
If everything uses aggressive dropping, the database can develop gaps.
If everything is acknowledged immediately, crashes can lose messages.
If everything is allowed to wait indefinitely, a downstream outage can block the MQTT connection.
The router and listener model provides a way to separate these requirements.
What the Implementation Demonstrates
Several practical lessons emerge from the design.
1. Acknowledgement should represent completed responsibility
Receiving a message is not necessarily the same as successfully processing it.
For durable storage, acknowledgement happens after MongoDB persistence or dead-letter handling.
2. At-least-once delivery means duplicates are possible
Preventing loss does not automatically provide exactly-once storage.
Applications need a stable message identity if duplicate deliveries need to converge on the same database record.
3. Message identity should come from stable data
A reception timestamp changes when a message is redelivered.
A publisher-provided identifier can remain stable.
That makes payload-level identity a more appropriate foundation for deduplication.
4. Backpressure needs boundaries
Backpressure protects memory, but an unlimited wait can create a dependency between consumers sharing the same broker connection.
A bounded wait provides a defined failure point.
5. Live streams and durable storage have different goals
A dashboard may prioritize freshness.
A database writer may prioritize completeness.
The architecture should reflect that difference.
6. MQTT metadata can matter as much as payload data
Retain state, expiry information, and MQTT 5 properties can affect how an application interprets the message.
Discarding those details can make the stored data less useful.
7. Subscription overlap needs to be considered
Two matching subscriptions can cause multiple deliveries.
A routing layer can reduce unnecessary overlap by avoiding redundant broker subscriptions.
8. Authentication and authorization are different
A user being authenticated to RESTHeart does not automatically mean every MQTT topic should be accessible.
Topic permissions need their own authorization check.
MQTT, MongoDB, and RESTHeart as a Unified Data Layer
One advantage of this architecture is that the application does not need a collection of unrelated services to perform basic MQTT-to-HTTP-to-MongoDB integration.
RESTHeart already provides several pieces required by the application:
- Authentication
- ACL-based authorization
- MongoDB APIs
- Server-Sent Events
- A plugin model
The MQTT module supplies the missing broker connection and routing behavior.
This allows MQTT data to participate in the same broader service architecture instead of requiring a separate application solely to bridge the broker and database.
For an application that already uses RESTHeart, this can reduce the amount of custom infrastructure required to expose MQTT data through HTTP and persist it in MongoDB.
Designing for Failure Instead of Assuming Success
Messaging systems often appear simple when everything is working.
The difficult engineering questions emerge during failure.
What happens when the application crashes?
What happens when MongoDB becomes slow?
What happens when a connection is re-established?
What happens when the broker redelivers a message?
What happens when two filters overlap?
What happens when a retained message arrives?
What happens when a payload is binary?
What happens when a user requests a topic outside their permissions?
The MQTT module’s design addresses these questions explicitly rather than assuming that the broker, database, and consumers will always operate at the same speed.
That is particularly important for IoT-style workloads, where publishers can continue producing data even when downstream services temporarily become unavailable.
A Practical Mental Model
A useful way to understand the architecture is to think of it as three layers.
Layer 1: MQTT transport
The broker is responsible for delivering MQTT publications according to the configured MQTT session and QoS behavior.
The RESTHeart MQTT connector maintains the broker connection.
Layer 2: Routing and delivery policy
The router determines which registered listeners receive each message.
Live and durable listeners can have different delivery policies.
This is where throttling, bounded queues, and consumer-specific behavior become important.
Layer 3: Application destination
The final destination depends on the consumer.
For a dashboard, the destination may be an SSE connection.
For persistent storage, the destination is MongoDB.
For a failed durable operation, the dead-letter file provides another destination.
This separation makes it possible to reason about each part independently while still maintaining a common MQTT connection.
What Operators Should Pay Attention To
The implementation also exposes several settings and behaviors that matter operationally.
The MQTT client needs an appropriate QoS and session configuration when durable redelivery is required.
A stable client ID is necessary for resuming the relevant MQTT session.
The database writer’s buffering configuration affects how the system behaves during temporary database problems.
buffer.max-wait-ms determines how long a message can wait for buffer space before being dropped.
The mqtt_buffer_dropped counter provides visibility into those drops.
Storage strategy determines how duplicate deliveries appear in MongoDB.
The auto strategy treats each delivery as a new document.
The payload-field strategy can use a stable identifier from the payload for an upsert.
Topic authorization determines whether a RESTHeart user can request a particular MQTT filter.
These are not isolated configuration details. Together they define the application’s delivery and failure behavior.
Why Real-World MQTT Testing Matters
Some of the most important implementation details were exposed during testing against Mosquitto.
Protocol documentation provides the rules, but actual integration testing helps reveal how those rules appear through the client API and application pipeline.
Retained messages, message expiry, repeated MQTT 5 user properties, overlapping subscriptions, and redelivery behavior all have consequences that may not be obvious when designing only the happy path.
Testing therefore needs to cover more than:
publish → receive → store
A useful test matrix should also include:
- Broker reconnects
- Application restarts
- MongoDB interruptions
- Slow database writes
- Buffer saturation
- Duplicate deliveries
- Retained messages
- Expiring messages
- Binary payloads
- MQTT 5 properties
- Overlapping topic filters
- Unauthorized topic filters
- Multiple RESTHeart instances
These scenarios are where the difference between a basic connector and a reliable integration becomes visible.
Final Thoughts
Connecting MQTT to MongoDB is easy to describe but much harder to make reliable.
The basic pipeline is straightforward:
MQTT → application → MongoDB
The real engineering challenge is deciding what should happen when one part of that pipeline is slower, restarts, disconnects, or receives the same message more than once.
The RESTHeart MQTT module takes a consumer-specific approach.
Live listeners are optimized for current information. They use bounded queues, rate limits, optional throttling, and message-drop accounting.
The MongoDB writer follows a more durable path. It uses backpressure, retries, dead-letter handling, and acknowledgement after the message has reached a durable destination.
MQTT session behavior can provide redelivery for messages that were never acknowledged, provided the appropriate QoS, session configuration, and stable client identity are used.
At-least-once delivery also means that duplicate messages are possible. Rather than attempting to manufacture an identity from changing reception timestamps, the payload-field strategy relies on a stable identifier supplied by the publisher.
The design also accounts for less obvious MQTT details.
Payloads can be text or binary.
MQTT 5 publish properties can contain repeated user-property names.
Retained messages can represent older values that are received by a new subscription.
Message expiry can arrive with the remaining interval already reduced by the broker.
Overlapping subscriptions can result in multiple deliveries.
Topic filters require authorization logic that understands MQTT wildcard containment rather than simple string equality.
Together, these details show why an MQTT integration layer needs to be designed around failure and delivery semantics, not just connectivity.
For applications that need both real-time MQTT data and persistent MongoDB records, the important architectural decision is to recognize that not every consumer needs the same guarantees.
A dashboard may need the newest value.
A database needs a reliable history.
A good integration layer should allow both requirements to coexist without allowing one consumer’s failure mode to unnecessarily become another consumer’s outage.
That is the role of the RESTHeart MQTT module: provide a common broker connection while allowing each consumer to use the delivery behavior appropriate to its purpose.



