Azure Service Bus vs Valkey & Redis Based Reliable Queue and PubSub
Azure Service Bus is Microsoft's managed message broker. You create a namespace and Microsoft runs everything under it: queues for point-to-point work, topics and subscriptions for publish/subscribe, and a settlement model built around message locks. For a lot of teams on Azure it is the right answer, and nothing below should talk them out of it.
This comparison is for a narrower case: a JVM application that already runs Valkey or Redis (Azure Managed Redis, Azure Cache for Redis, or its own) and a messaging workload that keeps running into a Service Bus limit. Redisson PRO's Reliable Queue and Reliable PubSub put the same delivery semantics on that data store (acknowledgments, visibility timeouts, delivery limits, dead-letter destinations), add per-message priority, delayed retries and replay, and ship a JMS 2.0, 3.0 and 3.1 implementation on top. If your consumers are .NET, Python or Node, or are Azure Functions triggers, you can stop reading: Redisson is a Java library, and that alone settles the question.
Redisson is our product, so give the section on what you give up the same weight as the rest. The Service Bus figures come from Microsoft Learn and the Azure SDK for Java as published in November 2026. The Redisson behaviour comes from its documentation, the 4.8.0 API and our engineering team.
Why Java Teams on Service Bus Are Taking Another Look
The original Java SDK is out of support. Microsoft retired com.microsoft.azure:azure-servicebus on 30 September 2026. It keeps working, but it no longer receives official support or updates. Its replacement, com.azure:azure-messaging-servicebus, has a different API. Clients are built with ServiceBusClientBuilder. Sender, receiver and processor clients replace QueueClient and registerMessageHandler, and the asynchronous API moves from CompletableFuture to Project Reactor. Teams still on the old library have a messaging rewrite ahead of them, and a rewrite is the cheapest moment to reconsider what sits underneath.
JMS means Premium. Service Bus implements the JMS 2.0 API only on the Premium tier, through Microsoft's azure-servicebus-jms library; Standard supports only a JMS 1.1 subset focused on queues. Premium is billed per messaging unit per hour whether messages flow or not, so a team that wants JMS 2.0 on Azure pays for dedicated capacity to get it.
The Redis side is moving too. Azure Cache for Redis retires its Enterprise and Enterprise Flash tiers on 31 March 2027 and its Basic, Standard and Premium tiers on 30 September 2028, in favour of Azure Managed Redis. Java teams moving their cache are already working on the data store that Reliable Queue and Reliable PubSub would run on, which makes this a sensible time to compare the two.
Azure Service Bus vs Reliable Queue and Reliable PubSub at a Glance
| Redisson PRO Reliable Queue and Reliable PubSub | Azure Service Bus | |
|---|---|---|
| Deployment | Java library on Valkey or Redis that you run or rent, on Azure or anywhere else | Managed namespace in Azure: Basic, Standard or Premium tier |
| Clients | Java / JVM only, over RESP | SDKs for .NET, Java, Python, JavaScript and Go, plus any AMQP 1.0 client; HTTP/REST |
| Queues | RReliableQueue | Queues, on every tier |
| Publish/subscribe | RReliablePubSubTopic: topic → subscription → consumer | Topics and subscriptions, on Standard and Premium (not Basic) |
| Settlement | Acknowledge, or negatively acknowledge as failed or rejected; manual by default | Complete, abandon, dead-letter or defer; peek-lock or receive-and-delete |
| Lock / visibility timeout | visibility per queue, subscription or poll; default 30 s, no documented maximum | Lock duration default 1 minute, maximum 5 minutes; renew it to hold longer |
| Retry delay | Yes: failed(id).delay(…) | None. An abandoned message goes back to the front of the queue at once |
| Delivery limit | deliveryLimit, default 10 | MaxDeliveryCount, default 10; can't be disabled |
| Dead-letter destination | Only if deadLetterQueueName or deadLetterTopicName is set; otherwise the message is deleted | Automatic dead-letter sub-queue on every queue and subscription; can't be deleted |
| Expired messages | Removed | Dropped, or dead-lettered if dead-lettering on expiration is on (off for new entities) |
| Priority | Per message, 0–9 | None. Microsoft's pattern routes a custom property to separate subscriptions |
| Scheduled delivery | delay per message, or a queue default | Scheduled enqueue time, cancellable by sequence number; no recurring schedules |
| Deduplication | By ID or payload hash, window set per message | By MessageId (plus partition key on partitioned entities); window 20 s to 7 days, default 10 minutes; not on Basic |
| Ordering | SEQUENTIAL processing mode (queue); message groups on PubSub since Redisson PRO 4.0.0 (see sessions below) | Sessions: FIFO per session ID, exclusive session lock, session state; not on Basic |
| Routing and filtering | Reliable Fanout MessageFilter: a Java predicate over payload and headers, applied by the publisher | Broker-side SQL, Boolean and correlation filters with actions; properties only, not the body |
| Replay | Yes: topic retention modes, retainAfterAck() per subscription, and seek to earliest, latest, a message ID or a timestamp | None. A subscription only gets messages published while it exists; completed messages are gone |
| Transactions | Each operation is atomic; no documented transaction spanning several | Send, complete, abandon, dead-letter, defer and renew in one transaction; cross-entity through send-via; 2-minute timeout; not on Basic |
| Message size | maxMessageSize; unset by default, so the Valkey/Redis 512 MB value limit applies | 256 KB on Basic and Standard; Premium up to 100 MB over AMQP, 1 MB per batch |
| Backlog size | Bounded by memory; maxSize caps the message count and creates backpressure | 1–5 GB per entity on Basic and Standard (80 GB partitioned); 80 GB on Premium |
| Throughput | Whatever your Valkey or Redis deployment sustains | Standard: 1,000 credits per second per namespace. Premium: dedicated messaging units |
| JMS | JMS 2.0, Jakarta Messaging 3.0 and 3.1; passes all TCK tests | JMS 2.0 on Premium only, with documented gaps; Standard has a queue-focused JMS 1.1 subset |
| Replication | Inherited from Valkey/Redis; syncMode ACK or ACK_AOF per operation | Managed in-region; Geo-Replication of data to a secondary region on Premium |
| Cost model | Your Valkey/Redis capacity plus a PRO licence; no per-operation charge | Basic and Standard per operation, counted in 64 KB units; Premium per messaging unit per hour |
| Local development | Any Valkey or Redis container | Emulator in Docker: no JMS, no persistence across restarts, 256 KB messages |
How the Messaging Models Map
Service Bus gives you two entity types in one namespace. Redisson covers the same ground with three objects, plus the JMS layer that sits on two of them.
Queues and Reliable Queue
A Service Bus queue and an RReliableQueue are the same idea: competing consumers, each message delivered to one of them, held invisible while it is processed and redelivered if it isn't settled. Here is a worker on the current Java SDK, the one users of the retired library are moving to:
ServiceBusProcessorClient processor = new ServiceBusClientBuilder()
.connectionString(connectionString)
.processor()
.queueName("orders")
.disableAutoComplete() // settle explicitly
.maxAutoLockRenewDuration(Duration.ofMinutes(10)) // default is 5 min
.maxConcurrentCalls(8)
.processMessage(context -> {
try {
process(context.getMessage().getBody().toObject(Order.class));
context.complete();
} catch (MalformedOrderException e) {
context.deadLetter(new DeadLetterOptions()
.setDeadLetterReason("malformed")
.setDeadLetterErrorDescription(String.valueOf(e)));
} catch (RuntimeException e) {
context.abandon(); // redelivered at once
}
})
.processError(error -> log("Service Bus error", error.getException()))
.buildProcessorClient();
processor.start();
The same worker on Reliable Queue:
RReliableQueue<Order> queue = redisson.getReliableQueue("orders");
// No dead-letter queue name means exhausted messages are deleted
queue.setConfig(QueueConfig.defaults()
.deliveryLimit(10) // MaxDeliveryCount
.visibility(Duration.ofMinutes(1)) // LockDuration
.deadLetterQueueName("orders-dlq"));
while (running) {
Message<Order> message = queue.poll(QueuePollArgs.defaults()
.timeout(Duration.ofSeconds(30)) // long poll
.visibility(Duration.ofMinutes(20))); // sized for the longest job
if (message == null) {
continue;
}
try {
process(message.getPayload());
queue.acknowledge(QueueAckArgs.ids(message.getId()));
} catch (MalformedOrderException e) {
queue.negativeAcknowledge(QueueNegativeAckArgs.rejected(message.getId()));
} catch (RuntimeException e) {
queue.negativeAcknowledge(QueueNegativeAckArgs.failed(message.getId())
.delay(Duration.ofSeconds(30))); // retry in 30 s
}
}
The two workers are similar and differ in three places. The failure branch can ask for a delayed retry. The visibility window is chosen per poll, so a twenty-minute job asks for twenty minutes up front instead of relying on lock renewal. And the dead-letter queue has to be named. Service Bus creates one for every queue and subscription; Redisson creates nothing unless you set deadLetterQueueName, and a message that exhausts its delivery limit without one is deleted.
Topics, Subscriptions and Reliable PubSub
A Service Bus topic copies each message to every subscription, and each subscription behaves like a queue of its own, with competing consumers inside it. RReliablePubSubTopic has the same three layers (topic, subscription, consumer), with a delivery limit and dead-letter topic per subscription and a visibility window per subscription or per consumer:
RReliablePubSubTopic<OrderEvent> topic =
redisson.getReliablePubSubTopic("order-events");
// Once, at provisioning time, as you would declare it in Bicep or Terraform
topic.createSubscription(SubscriptionConfig.name("billing")
.deliveryLimit(10)
.deadLetterTopicName("billing-dlt")
.position(Position.earliest())); // start from retained history
// In every instance of the billing service
PushConsumer<OrderEvent> consumer = topic.getSubscription("billing")
.createPushConsumer(ConsumerConfig.name("billing-" + instanceId));
MessageListener<OrderEvent> listener = (message, ack) -> {
charge(message.getPayload());
ack.acknowledge(MessageAckArgs.ids(message.getId()));
};
// Per consumer; if unset, the subscription's visibility applies, then 30 s
consumer.registerListener(MessageListenerArgs.listener(listener)
.visibility(Duration.ofMinutes(1)));
topic.publish(PublishArgs.messages(
MessageArgs.payload(new OrderEvent("o-42", "created"))));
Pull consumers (pull and pullMany) work too, the same way the queue worker above does. Service Bus has no equivalent of Position.earliest(), which starts a new subscription at the topic's retained history (see replay below).
Subscription Rules and Reliable Fanout Filters
Service Bus subscriptions select their messages with rules the broker evaluates: SQL filters, correlation filters, and actions that rewrite properties on the subscription's copy. Filters see message properties, never the body, and Microsoft recommends correlation filters over SQL filters wherever possible, because SQL rules lower throughput for the subscription, the topic and the namespace.
Reliable PubSub subscriptions have no rule engine; each one receives every message. For routing, Redisson has Reliable Fanout, which publishes each message to its subscribed Reliable Queues in one atomic operation and applies an optional MessageFilter per queue, a serializable Java predicate over the payload and the headers:
RReliableFanout<Order> fanout = redisson.getReliableFanout("orders");
fanout.subscribeQueue("orders-eu",
(o, headers) -> "EU".equals(headers.get("region")));
fanout.subscribeQueue("orders-audit"); // no filter, like the default rule
fanout.publish(FanoutPublishArgs.messages(
MessageArgs.payload(order).header("region", order.region())));
The filter runs in the publishing client before the message is written, so it can read the payload as well as the headers, and its cost is publisher CPU, where a SQL rule costs namespace throughput. Redisson propagates it to every Reliable Fanout instance with the same name, which means every publishing service needs the filter's class on its classpath. What a filter can't do is rewrite the copy the way a rule action does. A subscribed queue that is full, has a message-size limit the message exceeds or rejects it as a duplicate doesn't get a copy, and publish() then returns null, so check what it returns. A topic carrying dozens of SQL rules that an operations team maintains in the portal is a different thing from a handful of predicates in code; read every rule before you decide it moves.
Sessions, Sequential Processing and Message Groups
Sessions are Service Bus's ordering feature. Messages with the same session ID go, in order, to whichever receiver holds that session's exclusive lock, and the session can carry state between messages, up to one message's size (256 KB on Standard, 100 MB on Premium). Basic doesn't have sessions, and once they are enabled on a queue or subscription, clients can no longer send or receive regular messages there.
Redisson covers part of this. Reliable Queue's SEQUENTIAL processing mode consumes messages strictly in arrival order with one message in flight, which is ordering for the whole queue rather than per key. Reliable PubSub has had message groups since Redisson PRO 4.0.0: messages published with the same group ID go to the same consumer in order, and a group passes to another consumer only when its owner has gone longer than groupIdClaimTimeout without taking the group's next message and without any other activity. The documentation is careful to add that the order of redelivered messages within a group isn't guaranteed. Session state has no equivalent; keep it in an RMap or RBucket keyed by the group ID.
Locks, Retries and Dead Letters
Lock Duration and Visibility
A Service Bus receiver in peek-lock mode holds a lock on each message, one minute by default and five minutes at most, so longer work has to renew it. The Java processor renews automatically, but only for maxAutoLockRenewDuration, five minutes by default. A job that outlives that window loses its lock, and the message goes to another consumer while the first is still working on it.
Reliable Queue grants a visibility window per poll and documents no maximum, so a long job asks for what it needs up front. Redisson documents no way to extend a window once it is granted, so you size it for the longest job. That has a cost in recovery: if the consumer dies a minute into a twenty-minute window, the message stays hidden for the other nineteen, while an unrenewed Service Bus lock lapses within its lock duration.
Retry Delay
When processing fails, Service Bus offers abandon, which releases the lock and returns the message to the front of the queue for immediate redelivery. AbandonOptions can modify properties but has no delay. A downstream outage therefore burns through all ten deliveries as fast as consumers can fetch them. Delayed retry is something you build. One common way is to complete the message and schedule a copy that carries your own attempt counter:
ServiceBusReceivedMessage received = context.getMessage();
Object previous = received.getApplicationProperties().get("attempt");
int attempt = 1;
if (previous instanceof Integer count) {
attempt = count + 1;
}
// The copy keeps the MessageId; a duplicate-detecting entity would drop it
ServiceBusMessage retry = new ServiceBusMessage(received);
retry.setMessageId(received.getMessageId() + "-retry-" + attempt);
// Your own counter: the copy's delivery count starts again
retry.getApplicationProperties().put("attempt", attempt);
sender.scheduleMessage(retry, OffsetDateTime.now().plusSeconds(30));
// If this fails after the schedule, the order runs twice
context.complete();
It works, with three caveats. The copy is a new message, so enforcing the attempt limit is now your code's job, not the broker's. The copy constructor keeps the original MessageId, and on an entity with duplicate detection a copy with the same ID inside the window is dropped without an error, so give it a new one. And the schedule and the complete aren't atomic unless you put both in a Service Bus transaction. With Reliable Queue the delay is an argument to the negative acknowledgment, failed(id).delay(…) in the worker above, and the delivery counter keeps counting. For the retry policy itself, see exponential backoff and jitter in Java.
Dead Letters
Service Bus gives every queue and subscription a dead-letter sub-queue that can't be deleted. It moves a message there after MaxDeliveryCount deliveries; that behaviour can't be disabled, only pushed out by raising the count. Dead-lettered messages stay until someone receives and completes them, because time-to-live isn't observed in the dead-letter queue. Expired messages are dropped unless dead-lettering on expiration is enabled, and new entities have it off. Your code can also dead-letter explicitly, with a reason and a description.
Reliable Queue's equivalents are deliveryLimit (also 10 by default), a rejected negative acknowledgment for the explicit case, and a dead-letter queue that exists only if you name it. That default is the most important thing to carry over in a migration: a Service Bus queue that relied on its automatic DLQ becomes, without deadLetterQueueName, a queue that deletes its poison messages. Expired messages are removed, never dead-lettered, and Redisson documents no setting to change that. The dead-letter queue guide compares the conventions across brokers, and dead-letter queues and topics with Redisson's JMS shows the JMS side.
Redrive
Redrive is simpler on the Redisson side. Resubmitting from Service Bus means receiving from the dead-letter sub-queue and sending each message again, or using Service Bus Explorer in the portal. A Redisson DLQ is an ordinary Reliable Queue, so once the fix has shipped, redrive is one call per batch:
RReliableQueue<Order> dlq = redisson.getReliableQueue("orders-dlq");
dlq.move(QueueMoveArgs.count(100).destination("orders")); // repeat until empty
Like a message resubmitted to Service Bus, a moved message starts with a fresh delivery count and gets the full delivery limit again. If the fix didn't take, every message goes through all its attempts again before it returns to the dead-letter queue, so redrive a few and watch them before you move a large backlog.
Ordering, Priority and Scheduling
Service Bus messages have no priority field. Microsoft's Priority Queue pattern implements it with a custom Priority property, SQL filters that route high and low into separate subscriptions, and a larger consumer pool on the high side. That works, and it turns one queue into a topic, several subscriptions and several consumer pools to size. Reliable Queue takes a priority from 0 to 9 on each message, and higher values are delivered first:
queue.addMany(QueueAddArgs.messages(
MessageArgs.payload(passwordReset).priority(9),
MessageArgs.payload(weeklyDigest).priority(1)
.delay(Duration.ofHours(6))));
Priority schemes have a well-known failure mode, starvation of the low end. Distributed priority queues in Java covers it.
Both systems schedule delivery. Service Bus takes a scheduled enqueue time on the message, or a schedule call that returns a sequence number you can cancel with. Microsoft states no maximum horizon, doesn't support recurring schedules, and notes that the enqueue time is when the message becomes available, not when it is delivered. Reliable Queue takes a delay per message, as above, or a default per queue. Neither is a replacement for a scheduler running recurring jobs.
Ordering is covered under sessions above: per-key FIFO with state on Service Bus, whole-queue SEQUENTIAL processing or per-group order on Redisson. Either way, ordered messages are processed one at a time within their session, group or queue.
Replay and Retention
A Service Bus subscription is a virtual queue that receives a copy of each message published to the topic while the subscription exists. A subscription created today starts empty, and once a consumer completes a message, that message is gone. There is no seek and no rewind, and there's no way to hand a new consumer last week's events. On Azure, replay is Event Hubs' job, or a store of your own next to Service Bus.
Reliable PubSub keeps messages according to the topic's retention mode. The default, SUBSCRIPTION_OPTIONAL_RETAIN_ALL, stores every message in the topic whether or not any subscription exists, which is why the billing subscription above could start at Position.earliest() and read events published before it was created. Re-reading messages a subscription has already acknowledged takes one more setting. By default an acknowledged message is removed; with retainAfterAck() it stays in the topic, available for replay or audit, so create any subscription you mean to rewind with it. After that, rebuilding a read model from a bad deploy is a seek to the earliest retained message, the latest, a message ID or a timestamp:
// Created once with retainAfterAck(), so acknowledged messages can be re-read
topic.createSubscription(SubscriptionConfig.name("audit").retainAfterAck());
// After a bad deploy: re-read the last two hours
topic.getSubscription("audit")
.seek(Position.timestamp(Instant.now().minus(Duration.ofHours(2))));
The cost is memory, and the default makes it easy to miss. A topic that retains everything and has no timeToLive keeps growing in RAM, so set a TTL on any topic in the default mode. If you don't need replay, SUBSCRIPTION_REQUIRED_DELETE_PROCESSED discards a message once every subscription has finished with it. It only counts subscriptions that existed when the message was published, so like Service Bus it keeps nothing for subscribers that arrive later. The complete guide to Reliable PubSub covers the retention modes in detail.
Size, Throughput and Cost
Basic and Standard cap a message at 256 KB. Premium raises the ceiling to 100 MB per message over AMQP. The default is 1 MB per entity, raised queue by queue or topic by topic, while batches and HTTP sends stay at 1 MB (Microsoft's quotas page has the full list). Reliable Queue's maxMessageSize is unset by default, which leaves the Valkey/Redis 512 MB value limit as the ceiling. In practice a backlog of large messages in RAM becomes a capacity problem long before that.
Standard namespaces share capacity and are throttled at 1,000 credits per second per namespace. A message sent, received or peeked costs one credit, a management operation costs ten, and when a topic delivers to its subscriptions each filter evaluation costs one. The SDKs retry throttled calls automatically. Premium allocates dedicated messaging units (1, 2, 4, 8 or 16) and throttles when they run out of CPU or memory. Reliable Queue and Reliable PubSub run at whatever your Valkey or Redis deployment sustains. That cuts both ways: on a shared instance, a messaging spike competes with your cache for the same CPU.
The two bills differ in shape more than in size:
- Basic charges per million operations.
- Standard adds an hourly base charge that includes 13 million operations a month, then charges per million in tiers, plus brokered connections beyond the first thousand. An operation is any API call and is counted in 64 KB units, so a 96 KB message is two operations.
- Premium charges per messaging unit per hour, on the highest number allocated in each hour, whether or not messages flow. JMS 2.0, messages over 256 KB, private endpoints and Geo-Replication are Premium-only, so needing any one of them sets the floor of the bill.
- Reliable Queue and Reliable PubSub have no per-operation charge. You pay for the Valkey or Redis capacity the messages occupy, which you may already be paying for, plus a PRO licence: a fixed floor, then cost in capacity steps as backlog, retention and throughput grow, rather than per operation.
We don't publish a break-even table. Cloud prices move every quarter, and the answer depends on your volume, message size and how much of your Redis capacity is already spare.
JMS on Both Sides: What Moves Without a Rewrite
If your Service Bus code is written against JMS rather than the native SDK, the move can be a configuration change.
Service Bus implements the JMS 2.0 API on Premium through azure-servicebus-jms: com.azure:azure-servicebus-jms 2.x for the jakarta.jms namespace, com.microsoft.azure:azure-servicebus-jms 1.0.x for javax.jms. Microsoft's support table covers queues, topics, temporary destinations, queue browsers, all four subscription types, delivery delay, transacted sessions and selectors without LIKE or BETWEEN. noLocal and distributed transactions aren't supported. Microsoft's JMS-versus-SDK guide lists the bigger gaps. JMS can't receive from session-enabled entities, and the guide says plainly: "There's no workaround". It can dead-letter but can't set a reason, has no practical deferral, renews locks manually and has no batch receive.
Redisson PRO implements JMS 2.0 and Jakarta Messaging 3.0 and 3.1 (pro.redisson:redisson-jms-20, -30 and -31), backs queues with Reliable Queue and topics with Reliable PubSub, and, per its documentation, passes all TCK tests. The TCK is the specification's conformance suite, and it exercises the mandatory API, selectors and transacted sessions included. The docs show durable subscriptions, delivery delay, producer priority and sync settings. They don't describe how selectors are evaluated or how browsing behaves, so test the parts your code relies on. In Spring Boot the swap is one configuration class:
// The JMS class, not the redisson-spring-data class of the same name
import org.redisson.jms.RedissonConnectionFactory;
@Configuration
class MessagingConfig {
@Bean
RedissonConnectionFactory connectionFactory(RedissonClient redisson) {
RedissonConnectionFactory factory =
new RedissonConnectionFactory(redisson);
// Durable subscriptions need a client ID
factory.setClientId("orders-service");
// Service Bus created a dead-letter queue for you; here you name it
factory.setQueueConfig("orders", QueueConfig.defaults()
.deliveryLimit(10)
.deadLetterQueueName("orders-dlq"));
return factory;
}
// Topic listeners on Spring Cloud Azure refer to this bean by name
@Bean
DefaultJmsListenerContainerFactory topicJmsListenerContainerFactory(
ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory =
new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setPubSubDomain(true);
factory.setSubscriptionDurable(true); // the starter's default
return factory;
}
}
Remove spring-cloud-azure-starter-servicebus-jms and its spring.jms.servicebus.* properties. JmsTemplate, @JmsListener methods and message classes stay as they are. Topic listeners are the one exception: on Spring Cloud Azure they refer to the starter's topicJmsListenerContainerFactory bean by name, so the replacement keeps that name. Pick the artifact by namespace: redisson-jms-20 for javax.jms, -30 or -31 for jakarta.jms. Two things don't move. Destinations that other languages also read over AMQP stay where those readers are. And configuration that lives in the namespace rather than in your code (subscription rules and actions, auto-forwarding, dead-letter forwarding) has to be rebuilt on the Redisson side. JMS messaging over Valkey and Redis covers the API in full, and the Jakarta Messaging 3.1 walkthrough builds an example end to end.
Durability and Availability
Service Bus durability is Microsoft's job: the service handles in-region redundancy, and uses availability zones where the region has them. On Premium, Geo-Replication copies metadata and messages to a secondary region (one, currently). Synchronous replication gives an RPO of zero at the cost of latency. Asynchronous replication keeps latency low, but a forced promotion can lose messages or redeliver some. Promotion is always initiated by you, never by Azure. The older Geo-Disaster Recovery feature replicates metadata only, and the two can't be combined.
Reliable Queue and Reliable PubSub are exactly as durable as the Valkey or Redis deployment under them. Replication, persistence and failover are that deployment's settings. On Azure Managed Redis that means turning high availability on (primary and replica shards on separate nodes, spread across zones where the region supports them) and choosing data persistence. Azure Managed Redis also offers active geo-replication on most tiers, but it is not the counterpart of Service Bus Geo-Replication: it is active-active and eventually consistent, it can't be combined with data persistence, and Redisson doesn't document queue behaviour on it. Redisson adds per-operation control: syncMode ACK waits for replicas to acknowledge the write, and ACK_AOF also waits for the append-only file (Redis 7.2+ or Valkey). Check that your managed service supports the underlying WAIT and WAITAOF commands before relying on them. Also check one default: syncFailureMode is LOG_WARNING, so a failed sync is logged and the call still succeeds. If you chose Redisson for its durability controls, set THROW_EXCEPTION.
A Premium entity holds up to 80 GB on disk, while a Reliable Queue holds its backlog in RAM. Bound queues with maxSize so a stuck consumer pushes back on producers instead of filling the instance. Never put messages on an instance whose eviction policy can remove their keys: run messaging with noeviction, or keep the cache and the queues on separate instances.
How to Choose
| If this is true | Choose |
|---|---|
| .NET, Python or Node services consume the same queues | Stay on Service Bus |
| Consumers are Azure Functions or Logic Apps | Stay on Service Bus |
| Everything is on the JVM, you already run Valkey or Redis, and you need priority, delayed retries, replay or long visibility windows | Redisson PRO's Reliable Queue and Reliable PubSub |
| You pay for Premium mainly to get JMS 2.0 | Redisson PRO's JMS on the Redis you already run |
If Kafka, RabbitMQ or ActiveMQ are also on the list, the comparisons are on separate pages: Redis vs Kafka, Kafka vs Reliable PubSub, Redis vs RabbitMQ, RabbitMQ alternatives, and ActiveMQ vs Reliable Queue and Reliable PubSub. For the other two clouds, see Amazon SQS vs Reliable Queue and Google Pub/Sub vs Reliable PubSub.
Moving From Service Bus to Redisson, Step by Step
- Inventory the code. Search for
com.azure.messaging.servicebus,com.microsoft.azure.servicebus,ServiceBusJmsConnectionFactoryandspring.jms.servicebus. Native SDK code moves to Reliable Queue and Reliable PubSub; JMS code moves to Redisson's JMS artifact. - Inventory the namespace. Export every queue, topic, subscription and rule from Bicep, ARM, Terraform or
az servicebus. Much of a Service Bus setup lives in entity settings rather than code, and the table below maps them. - Prepare the data store. Decide whether messaging shares the cache instance or gets its own. Turn on high availability and persistence, set an eviction policy that can't drop message keys, choose
syncModefor the queues that matter, and setsyncFailureModetoTHROW_EXCEPTIONwhere a lost write must fail loudly. - Recreate what Service Bus did implicitly. Name a dead-letter queue or topic for every entity. Rebuild subscription rules as Fanout filters, and sessions as
SEQUENTIALqueues or as message groups on a Reliable PubSub topic. Make consumers idempotent if they weren't, since both systems deliver at least once. - Cut over one entity at a time. Point producers at Redisson, let the Service Bus consumers drain the old entity to zero, then stop them. Scheduled messages stay invisible until their time, and deferred messages can only be fetched by sequence number, so peek for both and move them across, or keep the old consumers running until they've cleared. Archive or replay the dead-letter sub-queues.
- Test the failure paths. Kill a consumer mid-message, exhaust a delivery limit, fill a
maxSizequeue, fail over the Redis primary, and check that each lands where you expect.
| Service Bus setting | Redisson equivalent | Watch for |
|---|---|---|
LockDuration | visibility (queue, subscription or poll) | No 5-minute cap, and no documented extension |
MaxDeliveryCount | deliveryLimit | Default 10 on both |
| Automatic dead-letter sub-queue | deadLetterQueueName / deadLetterTopicName | Required, or exhausted messages are deleted |
DefaultMessageTimeToLive | timeToLive | Expired messages are removed, never dead-lettered |
RequiresDuplicateDetection + history window | deduplicationById(id, window) or deduplicationByHash(window) | Set per message, not per entity |
RequiresSession | ProcessingMode.SEQUENTIAL, or message groups on PubSub | No session state |
MaxSizeInMegabytes | maxSize | Counts messages, not megabytes |
MaxMessageSizeInKilobytes (Premium) | maxMessageSize | Bytes, unset by default |
| Scheduled enqueue time | MessageArgs.delay(…) | A relative delay, not an absolute time |
| Subscription SQL or correlation rules | Reliable Fanout MessageFilter | Evaluated by the publisher; no actions |
ForwardTo (auto-forwarding) | Reliable Fanout to the target queue | Chains become explicit publishes |
AutoDeleteOnIdle | None | Remove idle queues yourself |
Reliable Queue and Reliable PubSub are Redisson PRO features, and their interfaces ship in the open-source jar. Code written against community Redisson compiles and then throws UnsupportedOperationException from getReliableQueue() at runtime. The open-source client has no getReliableFanout() method and no JMS classes, so that code doesn't compile without the PRO artifacts.
Frequently Asked Questions
Can Redis Replace Azure Service Bus?
For a JVM application that already runs Valkey or Redis, often yes. Redisson PRO's Reliable Queue and Reliable PubSub provide acknowledgments, visibility timeouts, delivery limits, dead-letter destinations, scheduled delivery and deduplication, and add per-message priority, delayed retries and replay, which Service Bus lacks. It isn't a replacement for polyglot clients, Azure Functions triggers, session state, broker-side SQL rules with actions, or cross-entity transactions. Plain Redis lists, Pub/Sub and Streams are a weaker substitute, because none of them gives you delivery limits or dead-lettering without application code.
Does Azure Service Bus Support Message Priority?
No. Service Bus messages have no priority property. Microsoft's Priority Queue pattern sets a custom Priority property and uses SQL filters to route messages into separate high- and low-priority subscriptions, each with its own consumer pool. Redisson PRO's Reliable Queue supports per-message priority from 0 to 9 on a single queue.
What Is the Maximum Lock Duration in Azure Service Bus?
Five minutes; the default is one minute. Work that takes longer must renew the lock. The Java processor client renews automatically for up to maxAutoLockRenewDuration, which defaults to five minutes, and after that the lock expires and the message is redelivered. Redisson's Reliable Queue sets a visibility timeout per queue or per poll with no documented maximum.
Does Azure Service Bus Support JMS?
Yes, JMS 2.0, but only on the Premium tier and through Microsoft's azure-servicebus-jms library: com.azure:azure-servicebus-jms 2.x for jakarta.jms, or com.microsoft.azure:azure-servicebus-jms 1.0.x for javax.jms. Selectors don't support LIKE or BETWEEN, noLocal and distributed transactions aren't supported, and JMS can't receive from session-enabled entities. The Standard tier supports only a JMS 1.1 subset focused on queues. Redisson PRO implements JMS 2.0 and Jakarta Messaging 3.0 and 3.1 and passes all TCK tests.
Is the Old Azure Service Bus Java SDK Still Supported?
No. Microsoft retired com.microsoft.azure:azure-servicebus on 30 September 2026. Applications using it keep working, but it no longer receives official support or updates. Its replacement is com.azure:azure-messaging-servicebus, which has a different, builder-based API with sender, receiver and processor clients, so moving to it means rewriting the messaging code.
Is Azure Service Bus Expensive?
It depends on the tier and the volume. Basic and Standard charge per operation, counted in 64 KB units, and Standard adds an hourly base charge that includes 13 million operations a month, which suits low and bursty traffic. Premium charges per messaging unit per hour whether or not messages flow, and it is the only tier with JMS 2.0, messages over 256 KB, private endpoints and Geo-Replication, so needing any of those sets the floor. Redisson PRO has no per-operation charge: the cost is the Valkey or Redis capacity plus a licence.
What Is the Difference Between Azure Service Bus Queues and Topics?
A queue delivers each message to one of its competing consumers. A topic copies each message to every subscription, and each subscription then behaves like its own queue with competing consumers, optionally filtered by rules. Topics need the Standard or Premium tier. In Redisson the equivalents are RReliableQueue and RReliablePubSubTopic, with Reliable Fanout for filtered routing into queues.
Does Reliable Queue Work With Azure Managed Redis and Valkey?
Redisson connects to Azure Managed Redis, Azure Cache for Redis, Valkey and self-hosted Redis. The client configuration depends on the Azure Managed Redis clustering policy: cluster mode for the OSS policy, single-server mode for the Enterprise and non-clustered policies. Valkey is wire-compatible with Redis, and the Redisson code on this page is the same for either. Check that your provider supports the WAIT and WAITAOF commands before relying on syncMode.
Is Reliable Queue Available in the Open-Source Edition?
No. Reliable Queue, Reliable PubSub, Reliable Fanout and the JMS implementation are Redisson PRO features. The Reliable Queue and Reliable PubSub interfaces ship in the open-source jar, so that code compiles against community Redisson and then throws UnsupportedOperationException at runtime. The open-source client has no getReliableFanout() method and no JMS classes, so code using them won't compile without the PRO artifacts.
Next Steps
All about Reliable Queue walks through acknowledgments, delivery limits and dead-letter routing, and handling backpressure covers bounding a queue with maxSize. For the concepts, see what a message queue is and publish/subscribe. On Azure, Redisson on Azure Cache for Redis and the managed Redis guide cover connection settings. Full configuration is in the queues and publish/subscribe documentation.
Reliable Queue, Reliable PubSub and the JMS implementation are part of Redisson PRO. You can try it for free.
Similar articles
- Amazon SQS vs. Reliable Queue for Valkey and Redis
- Google Pub/Sub vs Redis for Messaging
- Amazon Kinesis vs Valkey & Redis Based Reliable PubSub and Queue
- Apache Kafka vs Valkey & Redis Based Reliable PubSub
- Redis vs. RabbitMQ: Choosing the Right Message Broker
- ActiveMQ vs Valkey & Redis Based Reliable Queue and PubSub
- JMS Messaging Over Valkey and Redis