HiveMQ MQTT Client: Blocking vs Async vs Reactive API - Which Should You Use
I needed to add MQTT support to my Java application, and I chose HiveMQ MQTT Client because it seemed well-maintained. But then I hit a wall: the library offers three different API styles—blocking, async, and reactive. Which one should I use?
The documentation explains each style, but it doesn’t clearly say when to use which. After experimenting with all three, I finally understood the trade-offs. Let me share what I learned.
The Problem: Three APIs, No Clear Guidance
When I first looked at the HiveMQ MQTT Client, I saw this in the documentation:
buildBlocking()— synchronous operationsbuildAsync()— asynchronous with CompletableFuturebuildReactive()— reactive streams with backpressure
All three connect to the same MQTT broker and do the same things. But which one fits my use case?
Understanding the Three API Styles
1. Blocking API: The Straightforward Approach
The blocking API is the simplest. Every operation blocks the current thread until it completes.
Mqtt5BlockingClient client = Mqtt5Client.builder() .identifier("pub-" + UUID.randomUUID()) .serverHost("broker.hivemq.com") .serverPort(1883) .buildBlocking();
// This blocks until connection succeedsclient.connect();
// This blocks until the message is sentclient.publishWith() .topic("test/topic") .payload("hello".getBytes()) .send();
// Clean disconnect (also blocks)client.disconnect();When to use it:
- Simple publish-only scenarios
- Command-line tools or scripts
- When you don’t need to handle incoming messages
- When blocking a thread is acceptable (dedicated thread pools)
When NOT to use it:
- In reactive applications (Spring WebFlux, etc.)
- In high-throughput scenarios where thread blocking hurts performance
- When you need to subscribe to topics
The blocking client “works well when we don’t need to react to incoming messages,” according to the documentation. This was my first clue: if I need to subscribe and receive messages, blocking isn’t the right choice.
2. Async API: Event-Driven Without the Complexity
The async API uses CompletableFuture and callbacks. It’s non-blocking but doesn’t require learning a reactive framework.
Mqtt5AsyncClient client = Mqtt5Client.builder() .identifier("sub-" + UUID.randomUUID()) .serverHost("broker.hivemq.com") .serverPort(1883) .buildAsync();
// Connect returns a CompletableFutureclient.connect().thenAccept(connAck -> { System.out.println("Connected: " + connAck.getReasonCode());
// Subscribe after connection client.subscribeWith() .topicFilter("test/#") .send() .thenAccept(subAck -> { System.out.println("Subscribed successfully"); });});
// Set up callback for incoming messagesclient.publishes(MqttGlobalPublishFilter.SUBSCRIBED, publish -> { System.out.println("Received message on topic: " + publish.getTopic()); System.out.println("Payload: " + new String(publish.getPayloadAsBytes()));});I made a mistake here initially. I tried to use .join() on every CompletableFuture to wait for completion, essentially turning async into blocking:
// DON'T DO THIS - defeats the purpose of asyncclient.connect().join(); // Blocks!client.subscribeWith().topicFilter("test/#").send().join(); // Blocks!This defeats the purpose of the async API. Instead, chain operations with .thenCompose() or .thenAccept():
client.connect() .thenCompose(conn -> client.subscribeWith() .topicFilter("test/#") .send()) .thenAccept(subAck -> System.out.println("Ready to receive messages")) .exceptionally(ex -> { System.err.println("Failed: " + ex.getMessage()); return null; });When to use it:
- Subscribing to topics and handling incoming messages
- Event-driven applications
- When you want non-blocking without reactive frameworks
- Spring MVC or traditional Java applications
3. Reactive API: For Stream Processing
The reactive API is built on Reactive Streams (like Project Reactor). It’s ideal for high-throughput scenarios with backpressure support.
Mqtt5RxClient client = Mqtt5Client.builder() .identifier("rx-" + UUID.randomUUID()) .serverHost("broker.hivemq.com") .serverPort(1883) .buildRx();
// Connect returns a Single<Mqtt5ConnAck>client.connect() .flatMap(connAck -> client.subscribeStream() .topicFilter("sensors/#") .addSubscription() .execute()) // Returns Flowable<Mqtt5Publish> .flatMap(publish -> { // Process each message as a stream return processMessage(publish); }) .subscribe();The key advantage is backpressure. If your message processing can’t keep up with the incoming rate, the reactive API signals the broker to slow down.
When to use it:
- High-throughput stream processing
- Reactive applications (Spring WebFlux, Micronaut)
- When you need backpressure control
- Complex stream transformations
Decision Guide: Which API to Choose?
After experimenting, I created this decision tree:
┌─────────────────────────┐ │ Do you need to receive │ │ incoming messages? │ └───────────┬─────────────┘ │ ┌───────────┴───────────┐ │ │ NO YES │ │ ▼ ▼ ┌───────────────────┐ ┌─────────────────────────┐ │ Use BLOCKING │ │ Is your app reactive? │ │ (simplest) │ └───────────┬─────────────┘ └───────────────────┘ │ ┌───────────┴───────────┐ │ │ NO YES │ │ ▼ ▼ ┌───────────────────┐ ┌───────────────────┐ │ Use ASYNC │ │ Use REACTIVE │ │ (event-driven) │ │ (backpressure) │ └───────────────────┘ └───────────────────┘Practical Example: Combining Approaches
In my project, I needed both: a simple publisher and a message receiver. I used different clients for each:
// Publisher: Simple, infrequent, one-wayMqtt5BlockingClient publisher = Mqtt5Client.builder() .identifier("publisher-" + UUID.randomUUID()) .serverHost(brokerHost) .buildBlocking();
// Subscriber: Needs to react to incoming messagesMqtt5AsyncClient subscriber = Mqtt5Client.builder() .identifier("subscriber-" + UUID.randomUUID()) .serverHost(brokerHost) .buildAsync();
// Publisher connects, sends, disconnectspublisher.connect();publisher.publishWith() .topic("status") .payload("online".getBytes()) .send();publisher.disconnect();
// Subscriber stays connected and handles messagessubscriber.connect().join();subscriber.publishes(MqttGlobalPublishFilter.SUBSCRIBED, msg -> { handleIncomingMessage(msg);});Common Mistakes I Made
Mistake 1: Using Blocking API for Subscriptions
I initially tried to use the blocking API for receiving messages. It doesn’t work well because blocking operations don’t fit an event-driven model.
// Blocking client cannot handle incoming messages properlyMqtt5BlockingClient client = ...;client.connect();// There's no good way to wait for messages with blocking clientMistake 2: Not Handling CompletableFuture Exceptions
With the async API, I forgot to handle exceptions:
// Bad: Exceptions silently failclient.connect() .thenCompose(c -> client.subscribeWith().topicFilter("test").send());// If this fails, you'll never know
// Good: Always handle exceptionsclient.connect() .thenCompose(c -> client.subscribeWith().topicFilter("test").send()) .exceptionally(ex -> { log.error("Connection failed", ex); return null; });Mistake 3: Mixing API Styles in Reactive Applications
In a Spring WebFlux application, I tried using the async API with .join() everywhere. This blocked the reactor threads and caused performance issues. The reactive API is the right choice here.
Summary
| API Style | Best For | Thread Behavior | Complexity |
|---|---|---|---|
| Blocking | Publish-only, CLI tools | Blocks thread | Low |
| Async | Subscriptions, event-driven | Non-blocking with callbacks | Medium |
| Reactive | High-throughput, Spring WebFlux | Non-blocking with backpressure | Higher |
The key insight: Your use case determines the API, not the other way around.
- If you only publish, use blocking.
- If you subscribe and react, use async.
- If you process streams at scale, use reactive.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments