---
title: "ActiveMQ JMS 2.0 Implementation Guide: Simplified API, Transactions & Spring"
date: 2026-05-05
author: "TheFrameGuy"
featured_image: "https://www.meshiq.com/wp-content/uploads/blog_activeMQ-JMS-2_050826.jpg"
categories:
  - name: "Apache ActiveMQ®"
    url: "/sort-by/active-mq.md"
  - name: "Middleware Optimization"
    url: "/sort-by/middleware-optimization.md"
  - name: "Monitoring"
    url: "/sort-by/monitoring.md"
  - name: "MQ"
    url: "/sort-by/mq.md"
tags:
  - name: "devops"
    url: "/sort-by/tag/devops.md"
  - name: "monitoring"
    url: "/sort-by/tag/monitoring.md"
---

# ActiveMQ JMS 2.0 Implementation Guide: Simplified API, Transactions & Spring

JMS 2.0 changed this. The simplified API introduces JMSContext, which combines Connection and Session into a single AutoCloseable object. A producer sends a message in two lines of code. Exceptions are runtime instead of checked. The ceremony is gone.

This guide covers the full ActiveMQ JMS 2.0 implementation: the simplified API and how it compares to JMS 1.1, feature support across Apache ActiveMQ® and Apache Artemis™, transaction support with failover safety, delivery-delay configuration, shared subscriptions, and Spring Boot integration patterns that avoid the most common performance pitfalls.

## JMS 2.0 Support in ActiveMQ Apache ActiveMQ® and Apache Artemis™

Before writing any code, understand exactly which JMS 2.0 features are available on your broker and client version. Apache ActiveMQ®’s JMS 2.0 implementation is a work in progress, feature delivery happened across multiple 5.18.x and 6.x releases.

**Feature****Apache ActiveMQ® 5.18.x****Apache Artemis™ 2.x**JMS 2.0 API dependency✅✅JMSContext✅✅JMSProducer / JMSConsumer✅✅JMSRuntimeException (unchecked)✅✅receiveBody(Class)✅ (5.18.x+)✅Delivery delay (setDeliveryDelay)✅ (broker requires schedulerSupport=true)✅Async send with CompletionListener✅ (6.1.x+, 5.18.x backport)✅Shared non-durable subscriptions🔄 (planned 6.2.x)✅Shared durable subscriptions🔄 (planned 6.2.x)✅Jakarta Messaging 3.1 namespace✅ (6.x native; 5.18.x via activemq-client-jakarta)✅

For Artemis, full JMS 2.0 support has been available since the 2.x line. For Apache ActiveMQ®, check the Apache JIRA tracking page (activemq.apache.org/jms2) against your specific version before using any feature listed as in-progress.

The critical implication: **shared topic subscriptions are not available on Apache ActiveMQ® 5.18.x**. If your application needs multiple consumers on the same durable topic subscription (the JMS 2.0 createSharedDurableConsumer pattern), you have two options on Apache ActiveMQ®: use Artemis, or use Apache ActiveMQ®’s Virtual Topics, which predate JMS 2.0 and provide the same semantics.

We covered the Apache ActiveMQ® vs Apache Artemis™ architectural differences, including this feature gap, in our **[ActiveMQ Classic vs Artemis: 2026 Definitive Guide](https://www.meshiq.com/blog/apache-activemq-vs-apache-artemis/)**.

## The JMS 2.0 Simplified API: JMSContext, JMSProducer, JMSConsumer

### JMS 1.1 vs JMS 2.0: The Code Comparison

The scale of the simplification is best demonstrated by direct comparison. Both snippets below accomplish the same task: send a text message to a queue.

**JMS 1.1 (Apache ActiveMQ® API):**

**JMS 1.1 (Apache ActiveMQ® API):**Connection connection = null;  
try {  
 connection = connectionFactory.createConnection(“user”, “password”);  
 connection.start();  
 Session session = connection.createSession(false, Session.AUTO\_ACKNOWLEDGE);  
 MessageProducer producer = session.createProducer(queue);  
 TextMessage message = session.createTextMessage(“Order processed: ” + orderId);  
 message.setStringProperty(“orderId”, orderId);  
 message.setIntProperty(“priority”, 5);  
 producer.setPriority(5);  
 producer.send(message);  
} catch (JMSException e) {  
 // checked exception — must be handled or declared  
 logger.error(“Failed to send message”, e);  
 throw new RuntimeException(e);  
} finally {  
 if (connection != null) {  
 try { connection.close(); } catch (JMSException ignored) {}  
 }  
}

**JMS 2.0 (Simplified API):**try (JMSContext context = connectionFactory.createContext(“user”, “password”)) {  
 context.createProducer()  
 .setProperty(“orderId”, orderId)  
 .setPriority(5)  
 .send(queue, “Order processed: ” + orderId);  
}  
// JMSRuntimeException is unchecked — catch only if your code needs to handle itThe JMS 2.0 version does not require a Message object, does not require explicit session management, does not require separate close() calls, and does not require a checked exception handler. The JMSContext implements AutoCloseable, so the try-with-resources block handles cleanup regardless of how the block exits.

### JMSContext: Connection + Session in One Object

JMSContext is the entry point for all JMS 2.0 operations. Think of it as a Connection and Session merged into a single API. Create one from your ConnectionFactory:

// Java SE: default session mode is AUTO\_ACKNOWLEDGE  
JMSContext context = connectionFactory.createContext();  
  
// With credentials  
JMSContext context = connectionFactory.createContext(“username”, “password”);  
  
// With explicit session mode  
JMSContext context = connectionFactory.createContext(JMSContext.SESSION\_TRANSACTED);  
  
// Java EE / CDI injection (application server manages lifecycle)  
@Inject  
@JMSConnectionFactory(“jms/connectionFactory”)  
private JMSContext context;

**Session modes available on JMSContext:**

**Constant****Value****Behavior**AUTO\_ACKNOWLEDGE1Messages acknowledged automatically after onMessage() or synchronous receive() returnsCLIENT\_ACKNOWLEDGE2Application calls message.acknowledge() explicitly; all previously unacknowledged messages on the session are acknowledgedDUPS\_OK\_ACKNOWLEDGE3Lazy acknowledgment may produce duplicates if the broker fails, and reduces overheadSESSION\_TRANSACTED0Explicit commit() / rollback() required; used for transactional message processing

### JMSProducer: Method Chaining and Inline Properties

JMSProducer is a lightweight object, unlike JMS 1.1’s MessageProducer, you do not need to save it in a variable or close it. Create one on the fly from the context and chain your property settings directly into the send call:

// Send a string — no TextMessage creation needed  
context.createProducer().send(queue, “Hello world”);  
  
// Send with message properties (method chaining)  
context.createProducer()  
 .setProperty(“region”, “us-east”)  
 .setProperty(“priority”, 8)  
 .setDeliveryMode(DeliveryMode.PERSISTENT)  
 .setTimeToLive(300\_000L) // 5 minutes TTL  
 .send(topic, “Event fired”);  
  
// Send a serializable object directly  
context.createProducer().send(queue, new OrderEvent(orderId, amount));  
  
// Send a byte array  
context.createProducer().send(queue, new byte\[\]{0x01, 0x02, 0x03});

JMSProducer supports sending String, byte\[\], Map&lt;String, Object&gt;, Serializable, and Message objects directly, no type-specific message creation required for the primitive types.

### JMSConsumer: Receiving Without Casts

// Synchronous receive with type — no cast  
JMSConsumer consumer = context.createConsumer(queue);  
String message = consumer.receiveBody(String.class, 5000L); // 5s timeout  
  
// Returns null if timeout exceeded  
if (message == null) {  
 logger.warn(“No message received within timeout”);  
}  
  
// Asynchronous receive via MessageListener  
consumer.setMessageListener(message -&gt; {  
 String body = message.getBody(String.class); // JMS 2.0 type extraction  
 processOrder(body);  
});  
  
// Durable subscriber (persists subscription across disconnects)  
JMSConsumer durable = context.createDurableConsumer(topic, “my-subscription-name”);

## Transactions with JMS 2.0: Commit, Rollback, and Failover Safety

### Basic Transacted JMSContext

// Create a transacted context  
try (JMSContext context = connectionFactory.createContext(JMSContext.SESSION\_TRANSACTED)) {  
  
 JMSProducer producer = context.createProducer();  
 JMSConsumer consumer = context.createConsumer(inboundQueue);  
  
 // Process a message atomically: consume from one queue, produce to another  
 Message received = consumer.receive(5000L);  
 if (received != null) {  
 String orderId = received.getBody(String.class);  
 // Perform business logic…  
 producer.send(processedQueue, “Processed: ” + orderId);  
  
 // Commit both the consume acknowledgment and the produce  
 context.commit();  
 }  
  
} catch (JMSRuntimeException e) {  
 // Session is automatically rolled back when context closes on exception  
 logger.error(“Transaction failed, rolling back”, e);  
 throw e;  
}

### Handling In-Doubt Transactions During HA Failover

This is the aspect of JMS transactions that most applications get wrong. When a commit() call is in-flight at the exact moment the broker fails over, the Failover Transport cannot determine whether the commit reached the broker or not. The result is an in-doubt transaction that the broker automatically rolls back to prevent ambiguity.

From the client’s perspective, an in-doubt transaction manifests as a JMSRuntimeException wrapping a TransactionRolledBackException. The in-flight commit is rolled back, not replayed. Your application must catch this and retry the entire transaction from the beginning.

private void processWithRetry(String orderId, int maxAttempts) {  
 for (int attempt = 1; attempt &lt;= maxAttempts; attempt++) {  
 try (JMSContext context =  
 connectionFactory.createContext(JMSContext.SESSION\_TRANSACTED)) {  
  
 // All operations within the try block form one transaction  
 context.createProducer().send(warehouseQueue, “Pack: ” + orderId);  
 context.createProducer().send(shippingQueue, “Ship: ” + orderId);  
 context.commit();  
 return; // Success  
  
 } catch (JMSRuntimeException e) {  
 if (attempt == maxAttempts) {  
 logger.error(“Transaction failed after {} attempts for order {}”,  
 maxAttempts, orderId);  
 throw e;  
 }  
 logger.warn(“Transaction rolled back (attempt {}/{}), retrying: {}”,  
 attempt, maxAttempts, e.getMessage());  
 // Brief backoff before retry  
 try { Thread.sleep(100L \* attempt); } catch (InterruptedException ie) {  
 Thread.currentThread().interrupt();  
 throw new RuntimeException(ie);  
 }  
 }  
 }  
}  


Your retry logic must be idempotent, the same operation must produce the same result if executed multiple times. For order processing, this typically means using a unique order ID as a natural deduplication key and checking whether the order has already been processed before re-processing.

We covered the Failover Transport’s in-doubt transaction behavior in depth in our [**High Availability Architecture Guide**](https://www.meshiq.com/blog/activemq-high-availability-architecture/).

## Delivery Delay: Scheduling Messages in the Future

JMS 2.0 introduces setDeliveryDelay() on the producer, which instructs the broker to hold the message and deliver it only after the specified delay. On Apache ActiveMQ®, this feature relies on the broker’s scheduler, it is not a client-side hold.

### Required Broker Configuration

&lt;!– activemq.xml — enable scheduler support (REQUIRED for delivery delay) –&gt;  
&lt;**broker** xmlns=”http://activemq.apache.org/schema/core”  
 brokerName=”prod-broker”  
 schedulerSupport=”true”  
 dataDirectory=”/var/activemq/data”&gt;  
 &lt;!– dataDirectory must be set — scheduler data is stored on the shared path –&gt;  
 &lt;!– For HA setups, this must point to the shared filesystem –&gt;  
 &lt;!– Omitting this is the most common delivery delay misconfiguration –&gt;

Without schedulerSupport=”true”, the AMQ\_SCHEDULED\_DELAY property is silently ignored, and messages are delivered immediately. There are no error messages; it just arrives without the expected delay. This is consistently the most reported delivery delay bug report on the mailing list, and consistently has the same root cause: missing scheduler configuration.

Also note: the HA scheduler directory interaction we covered in the HA guide applies here, too. Scheduled messages are stored in the scheduler’s own data structure, if this is not on the shared filesystem in an Apache ActiveMQ® HA deployment, scheduled messages are lost on failover.

### JMS 2.0 Delivery Delay API

// JMS 2.0: set delivery delay on the producer  
try (JMSContext context = connectionFactory.createContext()) {  
 context.createProducer()  
 .setDeliveryDelay(30\_000L) // 30 seconds in milliseconds  
 .send(retryQueue, “Retry this operation”);  
}

### ActiveMQ Apache ActiveMQ®: AMQ\_SCHEDULED\_DELAY (Alternative for Pre-5.18.x)

For Apache ActiveMQ® versions before JMS 2.0 support, the proprietary scheduler API uses message properties:

// Classic scheduler via message properties (JMS 1.1 compatible)  
try (JMSContext context = connectionFactory.createContext()) {  
 // Create message explicitly to set ActiveMQ-specific property  
 TextMessage msg = context.createTextMessage(“Retry order: ” + orderId);  
 // Delay: 30 seconds  
 msg.setLongProperty(ScheduledMessage.AMQ\_SCHEDULED\_DELAY, 30\_000L);  
 // Optional: repeat delivery N times  
 msg.setIntProperty(ScheduledMessage.AMQ\_SCHEDULED\_REPEAT, 3);  
 // Optional: period between repeats  
 msg.setLongProperty(ScheduledMessage.AMQ\_SCHEDULED\_PERIOD, 10\_000L);  
 context.createProducer().send(retryQueue, msg);  
}

The AMQ\_SCHEDULED\_DELAY approach works on Apache ActiveMQ® 5.x regardless of JMS 2.0 support level and gives finer control (repeat counts, periods). It is the correct choice for scheduled delivery on Apache ActiveMQ® when the Apache Artemis™ migration is not planned.

## Async Sends with CompletionListener

JMS 2.0 introduced asynchronous send with a CompletionListener, the producer fires the send and receives a callback when the broker has acknowledged it, rather than blocking.

// Async send with CompletionListener (requires Classic 6.1.x+ or Artemis)  
try (JMSContext context = connectionFactory.createContext()) {  
 context.createProducer()  
 .setAsync(new CompletionListener() {  
 @Override  
 public void onCompletion(Message message) {  
 logger.info(“Message {} delivered successfully”,  
 message.getJMSMessageID());  
 }  
 @Override  
 public void onException(Message message, Exception exception) {  
 logger.error(“Message {} failed to deliver: {}”,  
 message.getJMSMessageID(), exception.getMessage());  
 // Handle failure: retry, DLQ routing, alert  
 }  
 })  
 .send(queue, “Event payload”);  
 // Returns immediately — broker acknowledgment arrives via callback  
}

**Important constraint:** the JMS 2.0 spec requires that the Message object is not accessed by the application after the send() call returns and before the CompletionListener is invoked. Do not re-use or modify the message object between the send and the callback.

The CompletionListener approach is the JMS 2.0 equivalent of useAsyncSend=true on the Apache ActiveMQ® connection factory (which we covered in [**ActiveMQ Performance Tuning: 10x Throughput**](https://www.meshiq.com/blog/activemq-performance-tuning/)), but with explicit per-message acknowledgment callbacks rather than fire-and-forget.

## Shared Topic Subscriptions: Scaling Topic Consumers

JMS 1.1 durable topic subscriptions have a fundamental limitation: only one consumer can be active at a time per subscription. If you want multiple threads or processes to consume from the same durable topic subscription for load balancing, JMS 1.1 has no built-in support.

JMS 2.0 introduces shared subscriptions, multiple consumers can share a single topic subscription, with the broker distributing messages among them:

// Shared durable consumer (available on Artemis; Classic 6.2.x planned)  
// Multiple JMSConsumer instances on the same subscription share the work  
try (JMSContext context = connectionFactory.createContext()) {  
  
 // Instance 1 (on one thread or service instance)  
 JMSConsumer consumer1 = context.createSharedDurableConsumer(  
 topic, “my-shared-subscription”);  
  
 // Instance 2 (on a different thread or service instance — same sub name)  
 JMSConsumer consumer2 = context.createSharedDurableConsumer(  
 topic, “my-shared-subscription”);  
  
 // Messages are distributed between consumer1 and consumer2  
 // rather than both receiving every message  
}

**Apache ActiveMQ® Virtual Topics as the equivalent:** Since shared subscriptions are not available on 5.18.x, the established pattern is Virtual Topics, an Apache ActiveMQ®-specific feature that predates JMS 2.0 and achieves the same consumer parallelism. Instead of a single topic subscription, each consumer team subscribes to a queue named Consumer.team1.VirtualTopic.MyTopic, which receives a copy of every message published to VirtualTopic.MyTopic. Multiple instances within the same team compete for messages on that queue, providing load balancing.

We covered Virtual Topics in the context of MQTT scalability in our [**MQTT Protocol Setup Guide**](https://www.meshiq.com/blog/activemq-mqtt-protocol-setup/). The same pattern applies to JMS consumers.

## Spring Boot Integration: JmsTemplate and @JmsListener

### The PooledConnectionFactory Requirement

This is the single most performance-damaging misconfiguration in Spring Boot JMS applications with ActiveMQ:

// WRONG: JmsTemplate without pooling  
// Creates a new connection + session + producer on EVERY send  
// Then immediately closes them all  
@Autowired JmsTemplate jmsTemplate; // backed by plain ActiveMQConnectionFactory

Every jmsTemplate.send() or jmsTemplate.convertAndSend() call opens a new connection to the broker, creates a session and producer, sends the message, and closes all three. For an application sending 100 messages per second, that is 100 connection open/close cycles per second, each involving a TCP handshake, authentication, and protocol negotiation with the broker.

// CORRECT: JmsTemplate with PooledConnectionFactory  
@Bean  
public ConnectionFactory connectionFactory() {  
 ActiveMQConnectionFactory activeMQCF = new ActiveMQConnectionFactory();  
 activeMQCF.setBrokerURL(“failover:(ssl://broker1:61617,ssl://broker2:61617)”  
 + “?randomize=false”);  
 activeMQCF.setUserName(“service-account”);  
 activeMQCF.setPassword(“service-password”);  
  
 // Pool connections — reuse across JmsTemplate calls  
 JmsPoolConnectionFactory pool = new JmsPoolConnectionFactory();  
 pool.setConnectionFactory(activeMQCF);  
 pool.setMaxConnections(10);  
 pool.setMaxSessionsPerConnection(100);  
 pool.setBlockIfSessionPoolIsFull(true);  
 pool.setBlockIfSessionPoolIsFullTimeout(5000L);  
 return pool;  
}  
  
@Bean  
public JmsTemplate jmsTemplate(ConnectionFactory connectionFactory) {  
 JmsTemplate template = new JmsTemplate(connectionFactory);  
 template.setDefaultDestinationName(“orders.queue”);  
 template.setDeliveryMode(DeliveryMode.PERSISTENT);  
 template.setSessionTransacted(true); // Enable JMS local transactions  
 return template;  
}

The JmsPoolConnectionFactory from org.messaginghub:pooled-jms (formerly activemq-jms-pool) is the correct pooling library. The older activemq-pool (PooledConnectionFactory) has known JMS 2.0 compatibility issues specifically, it throws AbstractMethodError when Spring’s JmsTemplate calls setDeliveryDelay() because the pool does not implement that JMS 2.0 method. Use the newer JmsPoolConnectionFactory for JMS 2.0 applications.

### Spring Boot @JmsListener Pattern

@Component  
public class OrderConsumer {  
  
 @JmsListener(destination = “orders.queue”,  
 containerFactory = “jmsListenerContainerFactory”)  
 public void onOrder(String orderPayload) {  
 // Spring auto-acknowledges after method returns without exception  
 // For exception: Spring rolls back and redelivers per redelivery policy  
 orderService.processOrder(orderPayload);  
 }  
  
 // Typed message reception with access to headers  
 @JmsListener(destination = “payments.queue”)  
 public void onPayment(Message&lt;PaymentEvent&gt; message) {  
 PaymentEvent event = message.getPayload();  
 String correlationId = message.getHeaders()  
 .get(“JMSCorrelationID”, String.class);  
 paymentService.process(event, correlationId);  
 }  
}  
  
// JmsListenerContainerFactory for topic subscriptions  
@Bean  
public DefaultJmsListenerContainerFactory topicListenerFactory(  
 ConnectionFactory connectionFactory) {  
 DefaultJmsListenerContainerFactory factory =  
 new DefaultJmsListenerContainerFactory();  
 factory.setConnectionFactory(connectionFactory);  
 factory.setPubSubDomain(true); // Topic mode  
 factory.setSubscriptionDurable(true); // Survive restarts  
 factory.setClientId(“my-service-” +  
 InetAddress.getLocalHost().getHostName()); // Unique per instance  
 factory.setSessionTransacted(true);  
 factory.setConcurrency(“1-5”); // Min 1, max 5 concurrent consumers  
 return factory;  
}

## JMS 1.1 to 2.0 Migration: What Changes and What Stays

The JMS 1.1 API is fully preserved in JMS 2.0. Existing code using Connection, Session, MessageProducer, and MessageConsumer continues to work without modification. Migration is incremental: introduce JMS 2.0 patterns in new code while leaving existing code as-is.

**What to adopt first (highest impact, lowest risk):**

1. **JMSContext with try-with-resources**: eliminates resource leak bugs in new code
2. **JMSProducer method chaining**: removes boilerplate MessageProperty setting
3. **receiveBody(Class)**: removes casting in consumer code

**What to handle carefully:**

- Exception handling, JMSRuntimeException is unchecked; verify that existing catch-all exception handlers still catch it

- Delivery delay requires broker configuration change (schedulerSupport=true)

- Shared subscriptions – Apache Artemis™ only (Apache ActiveMQ® 5.18.x not supported)

**Maven dependency for JMS 2.0 with Apache ActiveMQ®:**

&lt;!– pom.xml — JMS 2.0 client for ActiveMQ Classic –&gt;  
&lt;**dependency**&gt;  
 &lt;**groupId**&gt;org.apache.activemq&lt;/**groupId**&gt;  
 &lt;**artifactId**&gt;activemq-client&lt;/**artifactId**&gt;  
 &lt;**version**&gt;5.18.3&lt;/**version**&gt; &lt;!– or latest supported version –&gt;  
&lt;/**dependency**&gt;  
  
&lt;!– For Jakarta Messaging 3.1 namespace (Jakarta EE 9+) –&gt;  
&lt;!– Use activemq-client-jakarta in Classic &lt; 6.0 –&gt;  
&lt;!– In Classic 6.0+ this module is no longer needed –&gt;  
&lt;**dependency**&gt;  
 &lt;**groupId**&gt;org.apache.activemq&lt;/**groupId**&gt;  
 &lt;**artifactId**&gt;activemq-client-jakarta&lt;/**artifactId**&gt;  
 &lt;**version**&gt;5.18.3&lt;/**version**&gt;  
&lt;/**dependency**&gt;  
  
&lt;!– Pooling for Spring applications (JMS 2.0 compatible) –&gt;  
&lt;**dependency**&gt;  
 &lt;**groupId**&gt;org.messaginghub&lt;/**groupId**&gt;  
 &lt;**artifactId**&gt;pooled-jms&lt;/**artifactId**&gt;  
 &lt;**version**&gt;3.1.0&lt;/**version**&gt;  
&lt;/**dependency**&gt;

The transition from javax.jms to jakarta.jms namespace is handled by the activemq-client-jakarta module in 5.18.x, Apache ActiveMQ® existing Spring bean definitions do not change, only the import statements in your application code need updating when making the namespace switch. In Apache ActiveMQ® 6.0+, this module is no longer needed as the main client artifact uses Jakarta natively.

## **JMS 2.0 Is Ready for Production, With the Right Configuration**

JMS 2.0 delivers on its promise of simpler, less error-prone messaging code. The JMSContext + try-with-resources pattern eliminates the resource leak bugs that plagued JMS 1.1 codebases. The method chaining API eliminates the multi-step message property boilerplate. Unchecked exceptions remove the omnipresent throws JMSException from method signatures across entire application tiers.

The practical production prerequisites, pooled connection factories, broker scheduler configuration for delivery delay, idempotent retry for transactional failover, and careful version-checking against Apache ActiveMQ®’s rolling JMS 2.0 implementation are all addressable once you know they exist. This guide gives you the configuration foundation. meshIQ provides the operational expertise.

**Get expert guidance on your ActiveMQ JMS 2.0 implementation → Talk to an Expert**

## **Frequently Asked Questions**

**Q1. Does ActiveMQ support JMS 2.0?** 

Apache ActiveMQ® 5.18.x supports core JMS 2.0 features including JMSContext, JMSProducer, JMSConsumer, delivery delay, and async sends (added in 6.1.x). Shared topic subscriptions are planned for a later release. Apache Artemis™ provides full JMS 2.0 support. Verify your specific feature requirements against the Apache ActiveMQ® version’s JMS 2.0 tracking page before committing to an implementation.







**Q2. What is JMSContext in JMS 2.0?** 

JMSContext is the central object of the JMS 2.0 simplified API. It combines JMS 1.1 Connection + Session into a single AutoCloseable object, enabling try-with-resources usage and eliminating the connection management boilerplate that defined JMS 1.1 applications.







**Q3. What is the difference between JMS 1.1 and JMS 2.0?** 

JMS 2.0 adds the simplified API (JMSContext/JMSProducer/JMSConsumer), unchecked exceptions (JMSRuntimeException), delivery delay, async sends, and shared subscriptions. The JMS 1.1 API remains fully supported, migration to the simplified API is opt-in and incremental.







**Q4. How do I implement JMS transactions with JMS 2.0?** 

Use connectionFactory.createContext(JMSContext.SESSION\_TRANSACTED). Call context.commit() to commit and context.rollback() to roll back. Handle JMSRuntimeException from commit(), during HA failover, in-doubt transactions are rolled back. Implement idempotent retry logic to safely re-attempt failed transactions.







**Q5. How do I use JMS 2.0 with Spring Boot and ActiveMQ?** 

Add spring-boot-starter-activemq. Configure spring.activemq.broker-url in application.properties. Use @JmsListener for consumers. Critically: always wrap ActiveMQConnectionFactory in JmsPoolConnectionFactory (from org.messaginghub:pooled-jms), raw JmsTemplate without pooling creates a new connection on every send.