What Is JMS (Java Message Service)?
JMS (Java Message Service) is the standard Java API for sending, receiving, and reading messages between distributed applications. It defines a common set of interfaces that application code is written against, so a system can change the underlying message broker without rewriting its messaging logic. JMS is now formally known as Jakarta Messaging.
The most important thing to understand about JMS is what it is not. JMS is not a message broker, a server, or a piece of software you install. It is a specification — a set of interfaces that messaging vendors implement. The relationship is the same one JDBC has with databases: JDBC does not store data, it standardises how Java talks to whatever does. JMS does the same for messaging. Write your application against jakarta.jms interfaces, and swapping IBM MQ for ActiveMQ, or a dedicated broker for Redis, becomes a configuration change rather than a rewrite.
How JMS Works
A JMS application talks to a JMS provider — the messaging system that actually stores messages and delivers them. Your code holds interfaces; the provider supplies the implementation behind them.
Five objects do most of the work:
- ConnectionFactory — the entry point. An administered object that encapsulates connection settings and creates connections to the provider. Typically obtained through JNDI or constructed directly.
-
JMSContext — introduced in JMS 2.0, this combines a connection and a session into a single
AutoCloseableobject. The older API split these into separateConnectionandSessionobjects, which is still supported. -
Destination — the named target a message is sent to. It has two forms,
QueueandTopic, which correspond to the two messaging models described below. - JMSProducer — sends messages to a destination, with per-message controls for priority, delivery delay, and time-to-live.
-
JMSConsumer — receives messages from a destination, either by blocking on
receive()or asynchronously through aMessageListener.
A minimal send-and-receive looks like this:
try (JMSContext context = connectionFactory.createContext()) {
Queue queue = context.createQueue("orders");
context.createProducer().send(queue, "order-456");
String body = context.createConsumer(queue)
.receiveBody(String.class, 5000);
}
Message Types
Every JMS message carries headers, optional properties, and a body. The body determines the message type:
| Type | Body |
|---|---|
TextMessage | A String — in practice usually JSON or XML |
ObjectMessage | A serializable Java object |
BytesMessage | An uninterpreted stream of bytes |
MapMessage | Name/value pairs |
StreamMessage | A sequence of Java primitives |
Message | Headers and properties only, no body |
TextMessage and BytesMessage dominate real-world use. ObjectMessage couples producer and consumer to a shared class definition and is generally avoided in favour of an explicit serialization format.
Acknowledgement
A message is only safely delivered once the consumer confirms it. JMS defines several acknowledgement modes: AUTO_ACKNOWLEDGE, where the session confirms automatically once the consumer returns; CLIENT_ACKNOWLEDGE, where the application calls acknowledge() explicitly; and DUPS_OK_ACKNOWLEDGE, which permits lazy confirmation and therefore occasional duplicates. Sessions can also be transacted, grouping several sends and receives into a unit that commits or rolls back together.
Consumers can additionally filter what they receive using message selectors — SQL-like expressions evaluated against message headers and properties, so a consumer can subscribe to a busy destination but process only the subset it cares about.
JMS Messaging Models: Queues vs. Topics
JMS supports the two core messaging patterns, and the choice between them is the first design decision in any JMS system.
Point-to-point (queues). A message is sent to a queue and delivered to exactly one consumer. When several consumers read the same queue they act as competing workers, and the provider distributes messages among them. This is the model for task distribution and background job processing, where each unit of work must be performed once. It maps closely to a conventional message queue.
Publish/subscribe (topics). A message published to a topic is delivered to every interested subscriber, each receiving its own copy. This one-to-many fan-out suits event broadcasting — telling billing, analytics, and notification services that an order was placed. See our overview of publish/subscribe for more on the pattern.
| Queue | Topic | |
|---|---|---|
| Delivery | One consumer per message | Every subscriber gets a copy |
| Multiple consumers | Compete for messages | Each receives everything |
| Offline consumer | Message waits in the queue | Lost, unless the subscription is durable |
| Typical use | Job processing, task distribution | Event broadcast, notifications |
That last row is where most JMS confusion lives. A plain topic subscription only receives messages while the subscriber is connected — disconnect, and anything published in the meantime is gone. A durable subscription changes this: identified by a client ID and subscription name, it instructs the provider to retain messages for a subscriber that is temporarily offline and deliver them on reconnect. JMS 2.0 also added shared subscriptions, which let several consumers share one topic subscription and load-balance across it — effectively queue semantics layered onto a topic.
JMS, Jakarta Messaging, and the javax to jakarta Rename
The naming history behind JMS causes real confusion, particularly during upgrades.
The specification began as JMS 1.0 in 1998. JMS 1.1 (JSR 914) unified the queue and topic programming models so both could be used through one set of interfaces. JMS 2.0 (JSR 343) arrived in 2013 and introduced the simplified API — JMSContext, JMSProducer, JMSConsumer — that reduced typical boilerplate considerably.
Then the platform moved. Oracle transferred Java EE to the Eclipse Foundation, where it was renamed Jakarta EE, and JMS became Jakarta Messaging. For trademark reasons the javax package namespace could not move with it. So:
-
Jakarta Messaging 2.0 — functionally JMS 2.0, still in the
javax.jmsnamespace -
Jakarta Messaging 3.0 — identical API, but every package renamed from
javax.jmstojakarta.jms -
Jakarta Messaging 3.1 — the current version, refining the specification while keeping the
jakarta.jmsnamespace
The 3.0 release is the breaking one. Nothing about the messaging model changed; every import statement did. Applications moving from JMS 2.0 to Jakarta Messaging 3.x need their imports rewritten and a client library built for the matching namespace. For a practical walkthrough, see Jakarta Messaging 3.1 on Valkey and Redis.
JMS Providers and Brokers
Because JMS is only a specification, you need a provider to implement it. Common options include:
- Apache ActiveMQ Classic and ActiveMQ Artemis — the most widely deployed open-source JMS brokers
- IBM MQ — long-established enterprise messaging, common in finance and insurance
- Oracle WebLogic JMS — built into the WebLogic application server
- Open Liberty and WebSphere Liberty — embedded messaging engines for Jakarta EE applications
- Solace PubSub+ and TIBCO EMS — commercial enterprise messaging platforms
- Amazon SQS — usable from JMS through the Amazon SQS Java Messaging Library, though it covers a subset of the specification
- RabbitMQ — supports JMS through a dedicated client and broker plugin rather than natively
- Redisson PRO — a JMS implementation that passes all TCK tests, running on Redis or Valkey
One frequent question deserves a direct answer: Apache Kafka is not a JMS provider and is not JMS-compliant. Kafka's model is a partitioned, append-only log in which consumers track their own offsets. It has no equivalent of per-message acknowledgement, message selectors, or JMS durable subscription semantics. Bridges and JMS-style wrapper clients exist, but they emulate the API over a fundamentally different system rather than implementing the specification.
JMS vs. Kafka vs. AMQP
These three are often compared as if they were alternatives to each other. They are different categories of thing.
JMS is an API. It is Java-only and says nothing about how bytes travel across the network. Two JMS providers can be completely incompatible on the wire while exposing the same interfaces to your code.
AMQP is a wire protocol. It specifies the actual bytes exchanged between client and broker, and is language-agnostic. The two are complementary rather than competing — Apache Qpid, for instance, provides a JMS API that speaks AMQP 1.0 underneath.
Kafka is a platform with its own protocol and its own model, built for high-throughput streaming and message replay rather than the queue-and-topic semantics JMS describes.
For a fuller treatment, see our comparisons of Redis and Kafka, Kafka and Reliable PubSub, and Redis and RabbitMQ.
Using JMS With Spring
Most Java teams reach JMS through Spring rather than the raw API. The spring-jms module wraps the specification in two familiar abstractions.
JmsTemplate handles sending and synchronous receiving, managing connection and session lifecycles so application code does not have to. @JmsListener, enabled with @EnableJms, turns an ordinary method into a message-driven endpoint:
@JmsListener(destination = "orders")
public void handleOrder(String order) {
// Process the order
}
Spring targets queues by default. To use topics, set pubSubDomain to true on the JmsTemplate or on a DefaultJmsListenerContainerFactory. Spring Boot goes further and auto-configures the template as soon as a ConnectionFactory bean is present, with behaviour driven from application.properties:
spring.jms.pub-sub-domain=false
spring.jms.listener.concurrency=3
spring.jms.listener.max-concurrency=10
Any JMS provider that supplies a ConnectionFactory bean plugs into this machinery unchanged, which is what makes the provider a swappable decision rather than an architectural one. See the Spring integration documentation for configuration details, or Spring Cloud Stream with Redis for a binder-based alternative to JMS.
Running JMS on Redis and Valkey
Redis and Valkey are fast enough to serve as messaging infrastructure, and most teams already run one of them. What has historically been missing is a standards-based Java path into them — the native primitives offer pub/sub, streams, and list-based queues, but none of them speak JMS.
Redisson PRO closes that gap with a JMS implementation that passes all TCK tests — the Technology Compatibility Kit that verifies a provider against the specification — across JMS 2.0 (JSR 343), 3.0, and 3.1. Applications get standard JMS semantics without deploying or operating a separate broker cluster.
The implementation maps JMS concepts onto Redisson's messaging objects: JMS queues are backed by Reliable Queue (RReliableQueue) for point-to-point delivery, and JMS topics by Reliable PubSub (RReliablePubSubTopic) for publish/subscribe. Everything starts from a RedissonConnectionFactory, obtainable programmatically or declaratively through JNDI:
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
ConnectionFactory cf = new RedissonConnectionFactory(redisson);
try (JMSContext ctx = cf.createContext()) {
Queue queue = ctx.createQueue("orders");
ctx.createProducer().setPriority(7).send(queue, "order-456");
}
Because the JMS destinations are backed by Reliable Queue and Reliable PubSub, they inherit capabilities that go beyond the baseline specification — configurable delivery limits, per-message visibility timeouts, dead letter queues, message time-to-live, deduplication, delayed delivery, and strictly sequential processing where ordering matters. Durable subscriptions survive client restarts, and per-operation synchronous replication lets individual messages be confirmed on replicas, and optionally to an append-only file, before the operation returns.
Configuration can be set globally on the connection factory and overridden per destination, in Java or through JNDI properties. Full details are in the messaging documentation, with reference material for the underlying Reliable Queue and Reliable PubSub objects, and JMS Messaging Over Valkey and Redis works through a complete setup.
The JMS API is a Redisson PRO feature and can be evaluated with a free trial.
JMS: Frequently Asked Questions
What Does JMS Stand For?
JMS stands for Java Message Service. It is the standard Java API for sending and receiving messages between distributed applications. Following the move of Java EE to the Eclipse Foundation, it is now formally named Jakarta Messaging, though the JMS abbreviation remains in common use.
What Is JMS Used For?
JMS is used to let Java applications communicate asynchronously without calling each other directly. Common uses include distributing background jobs across workers, broadcasting events to multiple services in a microservices architecture, buffering traffic spikes so downstream systems are not overwhelmed, and integrating separate enterprise systems that cannot depend on each other being online at the same time.
What Is the Difference Between a JMS Queue and a Topic?
A queue delivers each message to exactly one consumer, so multiple consumers compete for work. A topic delivers a copy of each message to every subscriber. Queues are used for task processing where work should happen once; topics are used for broadcasting events to several interested parties.
Is Kafka JMS-Compliant?
No. Kafka uses a partitioned append-only log with consumer-managed offsets, a different model from the queue and topic semantics JMS defines. It has no native equivalent of per-message acknowledgement or JMS durable subscriptions. Wrapper clients and bridges can expose a JMS-like API over Kafka, but Kafka itself does not implement the specification.
What Replaced JMS?
Nothing replaced it — it was renamed. JMS became Jakarta Messaging when Java EE moved to the Eclipse Foundation and was rebranded as Jakarta EE. The API is the same; from Jakarta Messaging 3.0 onward the package namespace changed from javax.jms to jakarta.jms.
Is JMS Still Used?
Yes, particularly in enterprise Java systems in finance, insurance, logistics, and telecommunications, where large JMS deployments on IBM MQ, ActiveMQ, and WebLogic remain in production. Newer event-streaming platforms have taken over high-throughput streaming workloads, but JMS remains the standard interface for transactional queue and topic messaging in Java, and it continues to be maintained as Jakarta Messaging.
Do I Need a Separate Broker to Use JMS?
You need a JMS provider, but it does not have to be a dedicated broker cluster. Traditional providers such as ActiveMQ or IBM MQ run as standalone infrastructure. Alternatively, an implementation such as Redisson PRO provides JMS on top of Redis or Valkey, so teams already running one of those get JMS semantics without operating additional messaging infrastructure.