Messaging
Reliable Queue¶
For Reliable Queue usage please refer to this article.
Reliable Pub/Sub¶
For Reliable Pub/Sub usage please refer to this article.
JMS API implementation¶
This feature is available only in Redisson PRO edition.
Redisson implements Java Message Service API (JMS API) based on Valkey or Redis.
JMS 2.0 (JSR 343), JMS 3.0 and JMS 3.1 implementations are available.
The implementation uses RReliableQueue for point-to-point messaging and RReliablePubSubTopic for publish/subscribe messaging. It successfully passes all TCK tests.
There are two ways to obtain a ConnectionFactory: programmatically through the native Java API, or declaratively through JNDI. Both approaches produce the same RedissonConnectionFactory and support the same JMS operations once created.
Usage¶
Add dependency into your project.
Maven
<dependency>
<groupId>pro.redisson</groupId>
<!-- JMS 2.0 implementation -->
<artifactId>redisson-jms-20</artifactId>
<!-- JMS 3.0 implementation -->
<artifactId>redisson-jms-30</artifactId>
<!-- JMS 3.1 implementation -->
<artifactId>redisson-jms-31</artifactId>
<version>xVERSIONx</version>
</dependency>
Gradle
// JMS 2.0 implementation
compile 'pro.redisson:redisson-jms-20:xVERSIONx'
// JMS 3.0 implementation
compile 'pro.redisson:redisson-jms-30:xVERSIONx'
// JMS 3.1 implementation
compile 'pro.redisson:redisson-jms-31:xVERSIONx'
Native Java API¶
The native API gives full programmatic control over the connection factory, its Redisson client, and destination configuration.
Creating a Connection Factory¶
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.jms.RedissonConnectionFactory;
Config config = new Config();
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379")
.setTimeout(5000);
config.setThreads(16);
RedissonClient redisson = Redisson.create(config);
ConnectionFactory cf = new RedissonConnectionFactory(redisson);
The Redisson Config object supports all Redisson server modes: single server, master/slave, sentinel, cluster, and replicated. Refer to the Redisson documentation for the full range of options.
Factory-Level Settings¶
Queue, topic, and subscription behavior can be tuned per-destination or as a factory-wide default.
RedissonConnectionFactory cf = new RedissonConnectionFactory(redisson);
// Assigns a JMS client identifier to every connection created by this factory
cf.setClientId(id);
// Configure credential validation
cf.setUser(user);
cf.setPassword(password);
// Default for all queues
cf.setQueueConfig(myDefaultQueueConfig);
// Override for a specific queue
cf.setQueueConfig("orders", ordersQueueConfig);
// Default for all topics
cf.setTopicConfig(myDefaultTopicConfig);
// Override for a specific topic
cf.setTopicConfig("events", eventsTopicConfig);
// Subscription configuration (default and per-subscription)
cf.setSubscriptionConfig(myDefaultSubscriptionConfig);
cf.setSubscriptionConfig("mySub", mySubConfig);
Per-destination configuration takes precedence over the default. If neither is set for a given destination, the factory returns null and Redisson uses its own built-in defaults.
The available properties for each configuration type are listed in the Destination Configuration Properties section.
Sending and Receiving Messages¶
try (JMSContext ctx = cf.createContext()) {
Queue queue = ctx.createQueue("orders");
ctx.createProducer()
.setDeliveryDelay(1000)
.setPriority(7)
.send(queue, "order-456");
String body = ctx.createConsumer(queue)
.receiveBody(String.class, 5000);
}
Pub/Sub with Durable Subscriptions¶
try (JMSContext ctx = cf.createContext()) {
Topic topic = ctx.createTopic("events");
JMSConsumer subscriber = ctx.createDurableConsumer(topic, "my-durable-sub");
ctx.createProducer().send(topic, "event-1");
String event = subscriber.receiveBody(String.class, 5000);
}
Durable subscriptions persist across client restarts. Non-durable subscriptions are automatically deleted when the consumer closes.
Lifecycle¶
When you create a RedissonConnectionFactory with a Redis URL (the convenience constructor), the factory owns the Redisson client and you are responsible for shutting it down when done. When you supply your own RedissonClient, you manage its lifecycle independently.
The factory maintains a ref-counted internal scheduler for async delivery retry. The scheduler starts when the first connection opens and shuts down when the last connection closes. No explicit cleanup of the factory itself is required.
Spring JMS Configuration¶
Spring's JMS support works with any jakarta.jms.ConnectionFactory implementation. Since RedissonConnectionFactory implements the standard JMS ConnectionFactory, QueueConnectionFactory, and TopicConnectionFactory interfaces, it integrates directly with Spring's JmsTemplate, @JmsListener, and DefaultJmsListenerContainerFactory.
Java Configuration¶
Register a RedissonConnectionFactory as a Spring bean and enable JMS listener processing with @EnableJms. This is all that is required — Spring auto-configures a JmsTemplate and a default DefaultJmsListenerContainerFactory from the ConnectionFactory bean:
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.jms.RedissonConnectionFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.annotation.EnableJms;
import jakarta.jms.ConnectionFactory;
@Configuration
@EnableJms
public class JmsConfig {
@Bean
public ConnectionFactory connectionFactory(RedissonClient redisson) {
RedissonConnectionFactory cf = new RedissonConnectionFactory(redisson);
cf.setClientId("my-app");
// Queue config (optional)
cf.setQueueConfig("orders", QueueConfig.defaults()
.deliveryLimit(5)
.visibility(Duration.ofSeconds(60))
.deadLetterQueueName("orders-dlq"));
// Topic config (optional)
cf.setTopicConfig("events", TopicConfig.defaults()
.deliveryLimit(10)
.retentionMode(RetentionMode.SUBSCRIPTION_REQUIRED_DELETE_PROCESSED));
// Subscription config (optional)
cf.setSubscriptionConfig("auditSub", SubscriptionConfig.name("auditSub")
.deliveryLimit(20)
.deadLetterTopicName("events-dlt"));
return cf;
}
}
Pub/Sub with Topics¶
By default, Spring's JmsTemplate and DefaultJmsListenerContainerFactory target queues (point-to-point). To work with topics (pub/sub), set pubSubDomain to true.
For sending to topics, configure the JmsTemplate:
@Bean
public JmsTemplate jmsTopicTemplate(ConnectionFactory connectionFactory) {
JmsTemplate template = new JmsTemplate(connectionFactory);
template.setPubSubDomain(true);
return template;
}
For receiving from topics, configure a separate listener container factory:
@Bean
public DefaultJmsListenerContainerFactory topicListenerFactory(
ConnectionFactory connectionFactory) {
DefaultJmsListenerContainerFactory factory =
new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setPubSubDomain(true);
factory.setClientId("my-app");
return factory;
}
Then reference it in the listener:
@JmsListener(destination = "events", containerFactory = "topicListenerFactory",
subscription = "auditSub")
public void handleEvent(String event) {
// ...
}
Spring Boot Auto-Configuration¶
Defining a RedissonConnectionFactory bean is sufficient — Spring Boot's JMS auto-configuration detects it and creates the JmsTemplate and default JmsListenerContainerFactory automatically.
With this single bean, you can inject JmsTemplate and use @JmsListener without any further configuration. Spring Boot's spring.jms.* properties apply as usual:
# application.properties
spring.jms.pub-sub-domain=false
spring.jms.listener.concurrency=3
spring.jms.listener.max-concurrency=10
JNDI Lookup in Application Servers¶
When running inside an application server that exposes RedissonConnectionFactory through JNDI, Spring Boot can look it up directly. Configure the JNDI name in application.properties:
Spring Boot will retrieve the ConnectionFactory from JNDI and use it for JmsTemplate and @JmsListener processing. No explicit bean definition is needed.
For non-Boot Spring applications, use JndiObjectFactoryBean:
@Bean
public JndiObjectFactoryBean connectionFactory() {
JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
bean.setJndiName("java:/jms/RedissonConnectionFactory");
return bean;
}
JNDI¶
JNDI provides a declarative, configuration-driven approach. The RedissonInitialContextFactory reads environment properties, creates connection factories, queues, and topics, and binds them into a read-only JNDI context.
Basic Setup¶
Hashtable<Object, Object> env = new Hashtable<>();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"org.redisson.jms.jndi.RedissonInitialContextFactory");
// Connection factory
env.put("connectionfactory.myFactory.singleServerConfig.address",
"redis://127.0.0.1:6379");
env.put("connectionfactory.myFactory.registrationKey", "${REDISSON_KEY}");
env.put("connectionfactory.myFactory.clientId", "myClient");
// Destinations
env.put("queue.myQueue.name", "orders");
env.put("topic.myTopic.name", "notifications");
Context ctx = new InitialContext(env);
ConnectionFactory cf = (ConnectionFactory) ctx.lookup("myFactory");
Queue queue = (Queue) ctx.lookup("myQueue");
Topic topic = (Topic) ctx.lookup("myTopic");
The JNDI context is read-only. Attempts to call bind(), rebind(), unbind(), rename(), createSubcontext(), or destroySubcontext() throw OperationNotSupportedException.
Context Lifecycle¶
When the JNDI context is closed, all Redisson clients created during initialization are shut down automatically. This releases Redis connections and background threads. Connection factories obtained from the context should not be used after the context is closed.
Context ctx = new InitialContext(env);
try {
ConnectionFactory cf = (ConnectionFactory) ctx.lookup("myFactory");
// ... use cf ...
} finally {
ctx.close(); // shuts down Redisson clients
}
JNDI Environment Properties¶
All JNDI configuration is expressed as flat key-value properties in the JNDI environment Hashtable, in a PROVIDER_URL properties file, or in a jndi.properties file on the classpath.
Connection Factory Properties¶
Connection factory properties follow the pattern connectionfactory.<n>.<property>.
The <n> segment is the JNDI lookup name for the factory. Any key matching connectionfactory.<n>.* (three or more dot-separated segments) causes a factory with that name to be created. No separate declaration is needed — the presence of properties implies the factory.
| Property | Description |
|---|---|
connectionfactory.<n>.configFile |
Path or URL to a Redisson YAML configuration file. When present, inline Redisson properties are ignored for this factory. Supports variable expansion. |
connectionfactory.<n>.clientId |
JMS client identifier assigned to all connections from this factory. Required for durable subscriptions. Supports variable expansion. |
connectionfactory.<n>.<redissonProperty> |
Any Redisson configuration property in camelCase, matching YAML field names exactly. Passed through Redisson's PropertiesConvertor and parsed as YAML. |
Redisson properties use camelCase names that correspond directly to the field names in Redisson's YAML configuration format. Nested objects are expressed with dot notation.
Example of inline Redisson configuration:
connectionfactory.myFactory.singleServerConfig.address=redis://myhost:6379
connectionfactory.myFactory.singleServerConfig.timeout=5000
connectionfactory.myFactory.singleServerConfig.connectionMinimumIdleSize=10
connectionfactory.myFactory.threads=16
connectionfactory.myFactory.nettyThreads=32
connectionfactory.myFactory.registrationKey=${REDISSON_KEY}
connectionfactory.myFactory.clientId=myClient
Example using an external YAML file instead:
connectionfactory.myFactory.configFile=/etc/redisson/config.yaml
connectionfactory.myFactory.clientId=myClient
The connection factory prefix is case-insensitive (ConnectionFactory.myFactory.* also works), but the property names after the factory name are case-sensitive and must match Redisson's YAML field names exactly.
Multiple factories can be defined in the same environment:
connectionfactory.primary.singleServerConfig.address=redis://primary:6379
connectionfactory.primary.registrationKey=${REDISSON_KEY}
connectionfactory.secondary.singleServerConfig.address=redis://secondary:6379
connectionfactory.secondary.registrationKey=${REDISSON_KEY}
Queue Properties¶
Queue properties follow the pattern queue.<n>.<property>.
The <n> segment is the JNDI lookup name. The name property (the physical Redis queue name) is required. Additional properties configure the queue behavior and are applied to all connection factories in the context.
| Property | Description |
|---|---|
queue.<n>.name |
Physical Redis queue name (required). The JNDI name <n> is the lookup key; this value is the underlying queue name in Redis. Supports variable expansion. |
queue.<n>.<property> |
Queue configuration property. Maps to a method on Redisson's QueueConfigParams. See Queue Configuration Properties. |
Example:
queue.orders.name=physicalOrders
queue.orders.deliveryLimit=5
queue.orders.visibility=60000
queue.deadLetters.name=physicalDeadLetters
The old shorthand form queue.myQueue=physicalName (two segments, no .name) is not supported. Only the explicit form with a .name property creates a binding.
Topic Properties¶
Topic properties follow the pattern topic.<n>.<property>.
| Property | Description |
|---|---|
topic.<n>.name |
Physical Redis topic name (required). Supports variable expansion. |
topic.<n>.<property> |
Topic configuration property. Maps to a method on Redisson's TopicConfigParams. See Topic Configuration Properties. |
Subscription Properties¶
Subscription properties are nested under their parent topic: topic.<n>.subscription.<subName>.<property>.
| Property | Description |
|---|---|
topic.<n>.subscription.<s>.<property> |
Configuration property for subscription <s> on topic <n>. Maps to a method on Redisson's SubscriptionConfigParams. See Subscription Configuration Properties. |
Subscription properties require the parent topic to have a name property. If subscription properties are present without a topic name, a NamingException is thrown.
Example with topics and subscriptions:
topic.events.name=physicalEvents
topic.events.subscription.mySub.deliveryLimit=3
topic.events.subscription.mySub.deadLetterTopicName=physicalEvents-dlt
topic.events.subscription.anotherSub.deliveryLimit=10
topic.alerts.name=physicalAlerts
Configuration Application¶
Queue, topic, and subscription configurations are applied to all connection factories in the context. This is because destinations are shared resources — multiple factories pointing at the same Redis instance share the same physical queues and topics.
If no connection factories are defined (only queues and topics), configurations are parsed but not applied, and no error is thrown.
PROVIDER_URL¶
The java.naming.provider.url property specifies a path or URL to a .properties file containing additional environment properties. Properties from this file are merged into the environment, with the directly-specified environment entries taking precedence.
Accepted formats: a filesystem path (/etc/config.properties), a file: URL (file:///etc/config.properties), or an http:/https: URL. Windows-style file:// URLs with drive letters are handled correctly.
If the PROVIDER_URL is not set in the environment, the factory checks the java.naming.provider.url system property as a fallback.
Dynamic Destinations¶
Queues and topics can be looked up dynamically without pre-declaring them in the environment. The context exposes two sub-contexts for this purpose:
Queue queue = (Queue) ctx.lookup("dynamicQueues/myQueueName");
Topic topic = (Topic) ctx.lookup("dynamicTopics/myTopicName");
Dynamic destinations are created on first lookup with the lookup name as the physical destination name. They do not have configuration applied — they use the factory defaults.
Variable Expansion¶
All string values in the JNDI environment support ${variable} syntax. Variables are resolved in this order:
- Java system properties (
System.getProperty) - OS environment variables (
System.getenv) - The JNDI environment map itself
If a variable cannot be resolved, an IllegalArgumentException is thrown. To provide a fallback, use the ${variable:-default} syntax:
connectionfactory.myFactory.singleServerConfig.address=${REDIS_URL:-redis://localhost:6379}
queue.orders.name=${ORDER_QUEUE:-orders}
Variable expansion applies to clientId, configFile, queue names, topic names, and destination config property values. Inline Redisson properties (those passed through PropertiesConvertor) are not expanded by the JNDI layer — Redisson's own YAML parser handles ${variable} resolution from system properties and environment variables natively.
Property Value Type Conversion¶
Destination configuration property values are strings in the environment but are automatically converted to the type expected by the config class method:
| Target Type | Format | Example |
|---|---|---|
String |
Plain text | myValue |
int / Integer |
Decimal integer | 42 |
long / Long |
Decimal integer | 9999999999 |
boolean / Boolean |
true or false |
true |
double / Double |
Decimal number | 3.14 |
float / Float |
Decimal number | 1.5 |
Duration |
Milliseconds (numeric) or ISO-8601 | 5000 or PT30S |
For Duration, a plain numeric value is interpreted as milliseconds. Non-numeric values are parsed as ISO-8601 durations (e.g. PT5M for five minutes, PT30S for thirty seconds).
Method Resolution¶
Destination configuration properties are applied reflectively. For a property named deliveryLimit, the factory first looks for a JavaBean setter setDeliveryLimit(int). If not found, it tries a fluent method deliveryLimit(int). This accommodates both traditional setter-based APIs and Redisson PRO's fluent builder-style config classes.
If neither method is found on the config class, an IllegalArgumentException is thrown, wrapped in a NamingException.
Destination Configuration Properties¶
This section documents all available properties for queue, topic, and subscription configuration. These properties apply to both the native Java API (via fluent builder methods on the config objects) and JNDI (via flat key-value properties in the environment).
In the native API, properties are set using fluent methods on the config objects (QueueConfig.defaults().deliveryLimit(5)). In JNDI, the same property names are used as the final segment of the key (queue.orders.deliveryLimit=5).
Queue Configuration Properties¶
Queue configuration controls the behavior of RReliableQueue instances. In the native API, use QueueConfig.defaults() to create a config object; in JNDI, use queue.<n>.<property>.
| Property | Type | Default | Description |
|---|---|---|---|
deliveryLimit |
int |
10 |
Maximum number of delivery attempts for a message. Once reached, the message is moved to the dead letter queue if configured, otherwise deleted. Can be overridden per-message. |
visibility |
Duration |
30s |
Duration a message is invisible to other consumers after being polled. Prevents duplicate processing. If processing fails or crashes, the message reappears after this timeout. Can be overridden per-poll. |
timeToLive |
Duration |
0 |
Time-to-live for messages. Messages are automatically removed after this duration. 0 means no expiration. Can be overridden per-message. |
deadLetterQueueName |
String |
null |
Name of the dead letter queue for messages that reached the delivery limit or were rejected. The dead letter queue is a separate RReliableQueue instance. null disables dead-lettering. |
maxMessageSize |
int |
0 |
Maximum allowed size in bytes for a single message. Messages exceeding this size are rejected. 0 means no limit. |
delay |
Duration |
0 |
Delay before a message becomes available for consumption after being added. 0 means immediate availability. Can be overridden per-message. |
maxSize |
int |
0 |
Maximum number of messages that can be stored in the queue. When reached, add operations may block or return empty. 0 means no limit. |
processingMode |
String |
PARALLEL |
How messages are processed by consumers. SEQUENTIAL enforces one message at a time in strict order. PARALLEL allows concurrent consumption by multiple consumers. |
Native API example:
QueueConfig queueConfig = QueueConfig.defaults()
.deliveryLimit(5)
.visibility(Duration.ofSeconds(60))
.timeToLive(Duration.ofHours(24))
.deadLetterQueueName("orders-dlq")
.maxSize(10000)
.processingMode(ProcessingMode.SEQUENTIAL);
cf.setQueueConfig("orders", queueConfig);
JNDI example:
queue.orders.name=orders
queue.orders.deliveryLimit=5
queue.orders.visibility=60000
queue.orders.timeToLive=PT24H
queue.orders.deadLetterQueueName=orders-dlq
queue.orders.maxSize=10000
queue.orders.processingMode=SEQUENTIAL
Topic Configuration Properties¶
Topic configuration controls the behavior of RReliablePubSubTopic instances. In the native API, use TopicConfig.defaults() to create a config object; in JNDI, use topic.<n>.<property>.
| Property | Type | Default | Description |
|---|---|---|---|
deliveryLimit |
int |
10 |
Maximum delivery attempts for a message. Once reached, the message is moved to the dead letter topic if configured, otherwise deleted. Can be overridden per-subscription or per-message. |
visibility |
Duration |
30s |
Duration a message is invisible to other consumers after being polled. Can be overridden per-subscription, per-poll, or per-push-listener. |
timeToLive |
Duration |
0 |
Time-to-live for messages. Messages are automatically removed after this duration. 0 means no expiration. Can be overridden per-message. |
maxMessageSize |
int |
0 |
Maximum allowed size in bytes for a single message. 0 means no limit. |
delay |
Duration |
0 |
Delay before a message becomes available for consumption. 0 means immediate availability. Can be overridden per-message. |
maxSize |
int |
0 |
Maximum number of messages stored in the topic. When reached, publish operations may block or return empty. 0 means no limit. |
retentionMode |
String |
SUBSCRIPTION_OPTIONAL_RETAIN_ALL |
Controls message retention behavior. SUBSCRIPTION_REQUIRED_DELETE_PROCESSED — requires at least one subscriber; messages are discarded when all subscriptions have processed them. SUBSCRIPTION_REQUIRED_RETAIN_ALL — requires at least one subscriber; messages are never discarded. SUBSCRIPTION_OPTIONAL_RETAIN_ALL — subscribers not required; messages are always stored. |
Native API example:
TopicConfig topicConfig = TopicConfig.defaults()
.deliveryLimit(10)
.visibility(Duration.ofMinutes(2))
.timeToLive(Duration.ofDays(7))
.maxSize(50000)
.retentionMode(RetentionMode.SUBSCRIPTION_REQUIRED_DELETE_PROCESSED);
cf.setTopicConfig("events", topicConfig);
JNDI example:
topic.events.name=events
topic.events.deliveryLimit=10
topic.events.visibility=120000
topic.events.timeToLive=PT168H
topic.events.maxSize=50000
topic.events.retentionMode=SUBSCRIPTION_REQUIRED_DELETE_PROCESSED
Subscription Configuration Properties¶
Subscription configuration controls individual subscriptions on a topic. Each subscription maintains an independent offset and tracks message consumption separately. In the native API, use SubscriptionConfig.name("...") to create a config object; in JNDI, use topic.<n>.subscription.<s>.<property>.
| Property | Type | Default | Description |
|---|---|---|---|
deliveryLimit |
int |
10 |
Maximum delivery attempts for a message within this subscription. Overrides the topic-level deliveryLimit. Once reached, the message is moved to the dead letter topic if configured. |
visibility |
Duration |
30s |
Duration a message is invisible after being polled by a consumer in this subscription. Overrides the topic-level visibility. |
deadLetterTopicName |
String |
null |
Name of the dead letter topic for this subscription. Messages that reached the delivery limit or were rejected are sent here. The dead letter topic is a separate RReliablePubSubTopic instance. null disables dead-lettering. |
retainAfterAck |
boolean |
false |
When true, messages are retained in the subscription after acknowledgment instead of being discarded. |
Native API example:
Subscription<MyObject> sub = topic.createSubscription(
SubscriptionConfig.name("auditLog")
.deliveryLimit(20)
.visibility(Duration.ofMinutes(5))
.deadLetterTopicName("events-dlt")
.position(Position.earliest()));
cf.setSubscriptionConfig("auditLog", sub.getConfig());
JNDI example:
topic.events.name=events
topic.events.subscription.auditLog.deliveryLimit=20
topic.events.subscription.auditLog.visibility=300000
topic.events.subscription.auditLog.deadLetterTopicName=events-dlt
topic.events.subscription.auditLog.retainAfterAck=true
Complete JNDI Example¶
# jndi.properties
java.naming.factory.initial=org.redisson.jms.jndi.RedissonInitialContextFactory
# Primary factory — inline config
connectionfactory.primary.singleServerConfig.address=redis://${REDIS_HOST:-localhost}:6379
connectionfactory.primary.singleServerConfig.timeout=5000
connectionfactory.primary.threads=16
connectionfactory.primary.registrationKey=${REDISSON_KEY}
connectionfactory.primary.clientId=app-primary
# Secondary factory — YAML file
connectionfactory.secondary.configFile=${REDISSON_CONFIG_PATH}
connectionfactory.secondary.clientId=app-secondary
# Queues
queue.orders.name=app.orders
queue.orders.deliveryLimit=5
queue.orders.visibility=60000
queue.orders.deadLetterQueueName=app.orders-dlq
queue.deadLetters.name=app.orders-dlq
# Topics with subscriptions
topic.events.name=app.events
topic.events.deliveryLimit=10
topic.events.retentionMode=SUBSCRIPTION_REQUIRED_DELETE_PROCESSED
topic.events.subscription.auditLog.deliveryLimit=20
topic.events.subscription.auditLog.deadLetterTopicName=app.events-dlt
topic.events.subscription.analytics.deliveryLimit=3
topic.alerts.name=app.alerts
topic.alerts.visibility=120000
Context ctx = new InitialContext(); // reads jndi.properties from classpath
try {
ConnectionFactory primary = (ConnectionFactory) ctx.lookup("primary");
Queue orders = (Queue) ctx.lookup("orders");
Topic events = (Topic) ctx.lookup("events");
try (JMSContext jms = primary.createContext()) {
jms.createProducer().send(orders, "new order");
jms.createProducer().send(events, "order created");
}
} finally {
ctx.close();
}