In JMS, ActiveMQ is the broker (the server-side messaging process); Java producers and consumers are clients. A client connects to the broker, opens a session, addresses a queue, and sends or receives messages. The two clients can run in separate JVMs or on different machines as long as they can reach the broker.
This example uses the ActiveMQ Classic JMS API for the one-way queue code, then explains the equivalent Artemis choices. Keep the broker line, client libraries, package namespace, and configuration from the same product family and release.
How the queue flow works
- Start an ActiveMQ broker and note its connection endpoint.
- Create or look up a JMS connection factory.
- Open a connection and a session.
- Create a producer and consumer for the same queue.
- Start the connection before attempting delivery.
- Send a message from the producer and receive it with the consumer.
The broker owns delivery, persistence, acknowledgements, and queue state. The producer and consumer only hold client-side handles to that broker-managed destination.
Choose Classic or Artemis before writing code
ActiveMQ Classic and ActiveMQ Artemis are distinct broker lines. Their connection-factory classes, client artifacts, configuration files, and supported JMS namespace can differ. Do not copy an Artemis configuration into a Classic installation or assume that a Classic ActiveMQConnectionFactory works with an Artemis client.
#1 Best Overall
| Decision | What to use | Important qualification |
|---|---|---|
| Broker line | ActiveMQ Classic or ActiveMQ Artemis | Follow the documentation and client libraries for the selected line and release. |
| JMS namespace | javax.jms or jakarta.jms |
Match the namespace exposed by your client dependency; imports are not interchangeable. |
| Administered objects | JNDI lookup or direct construction | Artemis supports both approaches; use one consistently in a given application. |
Run an ActiveMQ Classic broker
Install an ActiveMQ Classic distribution, open a shell in its installation directory, and start the broker in the foreground:
bin/activemq console
The exact transport URI, port, security settings, and startup command can vary by distribution and release. Confirm them in the documentation shipped with the version you installed. Leave this process running while you start the Java clients.
One-way queue example with ActiveMQ Classic
The following classes use the Classic-style ActiveMQConnectionFactory. They illustrate the JMS lifecycle; supply a broker URI reachable from the client and the Classic client dependency that matches your installed release.
Producer
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSContext;
import javax.jms.JMSProducer;
import javax.jms.Queue;
import org.apache.activemq.ActiveMQConnectionFactory;
public class QueueProducer {
public static void main(String[] args) {
String brokerUri = "tcp://localhost:61616";
ConnectionFactory factory = new ActiveMQConnectionFactory(brokerUri);
try (JMSContext context = factory.createContext()) {
Queue queue = context.createQueue("OrderQueue");
JMSProducer producer = context.createProducer();
producer.send(queue, "order-1001");
System.out.println("Sent order-1001");
}
}
}
If your selected client exposes only the older JMS 1.x API, use its Connection, Session, MessageProducer, and TextMessage equivalents instead. The required API level is determined by the client library, not by the queue name.
Free tools Windows power users keep installed
One-click scans. No signup required.
Consumer
import javax.jms.ConnectionFactory;
import javax.jms.JMSContext;
import javax.jms.JMSConsumer;
import javax.jms.Queue;
import org.apache.activemq.ActiveMQConnectionFactory;
public class QueueConsumer {
public static void main(String[] args) {
String brokerUri = "tcp://localhost:61616";
ConnectionFactory factory = new ActiveMQConnectionFactory(brokerUri);
try (JMSContext context = factory.createContext()) {
Queue queue = context.createQueue("OrderQueue");
JMSConsumer consumer = context.createConsumer(queue);
String body = consumer.receiveBody(String.class, 10_000);
if (body == null) {
throw new IllegalStateException("No message received before timeout");
}
System.out.println("Received " + body);
}
}
}
Run order
- Start the broker.
- Start
QueueConsumer; it waits for a message for up to 10 seconds. - Start
QueueProducer. - Confirm that the consumer prints
Received order-1001.
A queue can be created automatically by a broker configured to allow it, but that is a broker policy rather than a JMS guarantee. For controlled environments, provision the queue explicitly and use the exact destination name in both clients.
Artemis: JNDI lookup versus direct construction
Artemis documentation demonstrates a durable broker queue named OrderQueue. Its client-side JNDI properties bind a lookup name to that server queue. Artemis’s JNDI implementation builds administered objects from client configuration; it does not require a separate server-side JNDI service.
JNDI approach
Configure the Artemis initial context with a connection-factory binding and a queue binding, then look up both objects:
ConnectionFactory factory = (ConnectionFactory) context.lookup("ConnectionFactory");
Queue queue = (Queue) context.lookup("OrderQueue");
try (JMSContext jms = factory.createContext()) {
jms.createProducer().send(queue, "order-1001");
}
The binding names are client configuration names. They map to the broker’s queue address; they do not create a second queue with a different identity.
Recommended Free Tools
Direct construction
Instead of JNDI, construct the Artemis connection factory with the Artemis client API and create a queue reference directly. The constructor and URI syntax vary by Artemis client version, so use the exact classes and endpoint format documented for that release. This option removes JNDI configuration from a small application but still requires the broker queue and address to be configured correctly.
Resource reuse is part of the design
JMS connections, sessions, producers, and consumers are designed to be reused. Create them during application startup, process many messages, and close them during shutdown. Creating a new connection or producer for every message adds setup overhead and can exhaust broker or client resources. A connection is normally shared by work in one application, while sessions must follow the concurrency rules of the JMS version and client implementation you use.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Request/reply with a temporary response queue
For a request/reply interaction, the client sends a request to a server queue and tells the server where to respond with JMSReplyTo. The client creates one temporary queue and one consumer for its lifetime, then reuses them for multiple requests.
- Create the request queue and a temporary response queue.
- Create a consumer for the temporary queue.
- For each request, create a message, set
JMSReplyToto the temporary queue, and assign a unique correlation ID. - Send the request.
- Have the server read
JMSReplyTo, produce its response, and copy the request’s correlation ID to the response. - Consume responses and match the returned correlation ID to the pending request.
TemporaryQueue replyQueue = session.createTemporaryQueue();
MessageConsumer replies = session.createConsumer(replyQueue);
TextMessage request = session.createTextMessage("price for SKU-42");
request.setJMSReplyTo(replyQueue);
request.setJMSCorrelationID("request-7");
requestProducer.send(request);
Message reply = replies.receive(10_000);
if (reply == null || !"request-7".equals(reply.getJMSCorrelationID())) {
throw new IllegalStateException("Missing or mismatched reply");
}
The server should send its response to the destination in JMSReplyTo and copy the correlation ID. A temporary queue per client avoids creating and destroying a destination for every request.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Common failures and checks
- Connection refused: verify that the broker is running, the URI uses the broker’s enabled transport, and the host and port are reachable from the client machine.
- Class or package errors: ensure every JMS import and dependency uses the same
javax.jmsorjakarta.jmsnamespace as the selected client. - Queue not found or messages remain undelivered: compare the broker queue/address name with the client’s destination or JNDI binding exactly, including case.
- Consumer receives nothing: start the connection (when using the classic Connection API), verify that the producer sent to the same queue, and check the receive timeout.
- Artemis/Classic configuration mismatch: replace the factory class, URI, dependency set, and broker settings with the coherent set for one broker line.
- Replies cannot be matched: require a unique correlation ID per request and copy it unchanged onto the response.
What to verify before production use
- Authentication, authorization, TLS, and broker network exposure.
- Durability and acknowledgement mode appropriate to message-loss and redelivery requirements.
- Explicit queue provisioning and dead-letter or retry policy.
- Consumer concurrency and session-threading rules for the chosen JMS client.
- Shutdown handling that closes consumers, sessions, connections, and temporary destinations cleanly.
The Bottom Line
The essential pattern is stable: one broker, a matching JMS client, a connection, session, producer and consumer, then a send and receive on the same queue. Choose ActiveMQ Classic or Artemis first, keep its API namespace and configuration together, and reuse JMS resources instead of creating them per message.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

