Skip to content
CMO & CTO
CMO & CTO

Closing the Bridge Between Marketing and Technology, By Luis Fernandez

  • Digital Experience
    • Experience Strategy
    • Experience-Driven Commerce
    • Multi-Channel Experience
    • Personalization & Targeting
    • SEO & Performance
    • User Journey & Behavior
  • Marketing Technologies
    • Analytics & Measurement
    • Content Management Systems
    • Customer Data Platforms
    • Digital Asset Management
    • Marketing Automation
    • MarTech Stack & Strategy
    • Technology Buying & ROI
  • Software Engineering
    • Software Engineering
    • Software Architecture
    • General Software
    • Development Practices
    • Productivity & Workflow
    • Code
    • Engineering Management
    • Business of Software
    • Code
    • Digital Transformation
    • Systems Thinking
    • Technical Implementation
  • About
CMO & CTO

Closing the Bridge Between Marketing and Technology, By Luis Fernandez

Messaging with JMS: Async without Fear

Posted on March 27, 2006 By Luis Fernandez

Messaging 101 with JMS: Async without Fear. I have been bouncing between web apps stuffed with AJAX and talks about services all week, and a theme keeps coming up in teams I visit: async is scary. People picture threads, stuck sockets, and mystery bugs at 2 AM. It does not have to be like that. With the Java Message Service, you can ship work off to queues, smooth out spikes, and keep user flows snappy. The trick is to think in messages and to keep your contracts clean. Tonight I want to boil down what is working for us with JMS right now with lessons that will still make sense when the next buzzword arrives.

Context

JMS is everywhere in the shop floor of Java servers. WebLogic ships with a solid provider. JBoss has queues out of the box. ActiveMQ from Apache is moving fast and is friendly for dev boxes. Plenty of teams still run WebSphere MQ in the back office. With Java 5 bringing nicer concurrency and annotations on the table, you might be wondering when to send a message instead of calling a method or an HTTP endpoint. If your app is serving logged in users and you need things to feel instant, asynchronous messaging is your friend. You confirm quickly, push the heavy work to workers, and let the broker buffer the rush when traffic spikes.

Definitions

Before we go to code, some quick terms that matter when you build real systems:

  • JMS: the Java API for messaging. It hides the provider details so your code can move between ActiveMQ, WebLogic JMS, WebSphere MQ, and friends.
  • Queue: point to point. One consumer gets each message. Use this for work items like email send, thumbnail build, payment post.
  • Topic: publish and subscribe. Many consumers can receive the same message. Use this for events like user signed up, order shipped, cache invalidated.
  • Persistent vs non persistent: persistent messages are stored by the broker so you do not lose them on restart. Non persistent are faster but volatile. For money or user data pick persistent.
  • Ack modes: AUTO is easy, CLIENT gives you control, DUPS OK is faster but you might see repeats. If you must be safe, start with AUTO or CLIENT plus idempotent logic.
  • Transactions: you can wrap send and receive in a transaction. This is useful to avoid losing messages on crash. Keep the scope small. XA across DB and JMS works but is heavy.
  • Message selectors: SQL like filters on headers and properties. Handy to route without new queues.
  • JMSCorrelationID and JMSReplyTo: keys for request and reply flows. Set ReplyTo so the worker knows where to answer. Use CorrelationID to match reply to request.
  • Dead letter queue: where poison messages go after retries. Always define one and monitor it.

Examples

Let us wire a basic producer and consumer first. This is plain JMS without any big framework.

import javax.jms.*;
import javax.naming.InitialContext;

public class InvoiceProducer {
  public static void main(String[] args) throws Exception {
    InitialContext ctx = new InitialContext(); // from jndi.properties
    ConnectionFactory cf = (ConnectionFactory) ctx.lookup("jms/ConnectionFactory");
    Queue queue = (Queue) ctx.lookup("jms/InvoiceQueue");

    Connection conn = cf.createConnection();
    Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
    MessageProducer producer = session.createProducer(queue);
    producer.setDeliveryMode(DeliveryMode.PERSISTENT);

    TextMessage msg = session.createTextMessage("{\"invoiceId\":1234}");
    msg.setStringProperty("tenant", "acme");
    msg.setJMSCorrelationID("req-1234");
    producer.send(msg);

    producer.close();
    session.close();
    conn.close();
  }
}

Now a consumer that does the work. Notice the simple error handling and the quick exit on failure. You need a retry plan and a dead letter queue behind this.

public class InvoiceConsumer implements MessageListener {
  public static void main(String[] args) throws Exception {
    InitialContext ctx = new InitialContext();
    ConnectionFactory cf = (ConnectionFactory) ctx.lookup("jms/ConnectionFactory");
    Queue queue = (Queue) ctx.lookup("jms/InvoiceQueue");

    Connection conn = cf.createConnection();
    Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
    MessageConsumer consumer = session.createConsumer(queue, "tenant = 'acme'");
    consumer.setMessageListener(new InvoiceConsumer());
    conn.start(); // start delivery

    System.out.println("Listening. Ctrl C to quit.");
  }

  @Override
  public void onMessage(Message message) {
    try {
      TextMessage tm = (TextMessage) message;
      String json = tm.getText();
      // do work
      System.out.println("Processing " + json);
      // if something fails, throw a RuntimeException to trigger redelivery
    } catch (Exception e) {
      throw new RuntimeException("work failed", e);
    }
  }
}

If you are using Spring, the JmsTemplate and the message listener container remove a lot of the ceremony.

// applicationContext.xml (snippet)
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
  <property name="brokerURL" value="tcp://localhost:61616"/>
</bean>

<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
  <property name="connectionFactory" ref="connectionFactory"/>
  <property name="pubSubDomain" value="false"/>
</bean>

<bean id="listenerContainer"
      class="org.springframework.jms.listener.DefaultMessageListenerContainer">
  <property name="connectionFactory" ref="connectionFactory"/>
  <property name="destinationName" value="jms/InvoiceQueue"/>
  <property name="messageListener" ref="invoiceHandler"/>
  <property name="sessionTransacted" value="true"/>
  <property name="concurrency" value="5"/>
</bean>
// Producer with Spring
jmsTemplate.convertAndSend("jms/InvoiceQueue", payload, m -> {
  m.setStringProperty("tenant", "acme");
  m.setJMSCorrelationID("req-1234");
  return m;
});
// Listener bean
public class InvoiceHandler implements MessageListener {
  public void onMessage(Message message) {
    // Spring wrapped transaction if configured
    // do work, throw unchecked to trigger rollback and redelivery
  }
}

Need request and reply without blocking the web thread? Send with JMSReplyTo, handle replies on a topic, and stash pending requests in a map keyed by JMSCorrelationID. A small sample:

// request
Destination replyTo = session.createTemporaryQueue();
TextMessage msg = session.createTextMessage("{\"cmd\":\"price\"}");
msg.setJMSReplyTo(replyTo);
msg.setJMSCorrelationID("abc-42");
producer.send(msg);

// reply listener
MessageConsumer replies = session.createConsumer(replyTo, "JMSCorrelationID = 'abc-42'");
replies.setMessageListener(m -> {
  // resolve promise, notify UI via comet or page refresh
});

On the server side you can also use message driven beans if you are on an app server. They keep the plumbing tidy and let the container retry on failure.

import javax.ejb.MessageDriven;
import javax.jms.*;

@MessageDriven(mappedName = "jms/InvoiceQueue")
public class InvoiceBean implements MessageListener {
  public void onMessage(Message message) {
    // do work
  }
}

The message driven bean route is great when your team already runs an app server and you want simple deployment and pooling. The Spring route shines when you want the same model in a plain Tomcat world.

Counterexamples

There are times when a message queue is the wrong tool.

  • Read heavy low latency requests: fetching product details or user profile fits HTTP or direct calls. A queue just adds delay.
  • Large payloads: video files or big binary blobs do not sit well in a broker. Store in a file server or S3 like storage and pass a pointer in the message.
  • Cross service transactions with two phase commit: it can be done, but the cost in setup and failure cases is steep. Many teams keep DB and JMS work in separate small steps and reach eventual consistency with retries.
  • Strict ordering across many keys: a single queue preserves order only per consumer. If your business needs global order you will hit throughput limits fast.
  • When ops is not ready: no monitoring, no disk plan, no backup, no dead letter queues. That is a recipe for a bad night.

Decision rubric

When a team asks me if JMS fits, we go through these checks. If most boxes say yes, I lean to messages.

  • Does the user need instant feedback while heavy work continues later? Yes points to queue.
  • Can the work be expressed as an independent command or event? Yes points to queue or topic.
  • Is repeat delivery acceptable if your handler is idempotent? Yes points to simpler ack modes.
  • Do you need to buffer bursts? Yes favors a broker with persistent messages.
  • Do you have a clear retry plan and a dead letter queue? Yes is a must before go live.
  • Is the payload small and self contained? Yes is good for a message. If not, pass a reference.
  • Do you need fan out to many services? Yes points to topics with durable subscribers.
  • Do you really need XA? If you are unsure, start without XA. Use a transactional session for the consumer and keep DB writes and send in separate steps with retries.
  • Is monitoring in place? Queues, consumers, pending counts, dead letters, average age. If you cannot see it, you cannot fix it.

One more tip. If you lose sleep over a message being processed twice, add a business id to the payload and keep a table of processed ids. Then your consumer becomes safe to repeat. That pattern unlocks AUTO ack and less stress.

Lesson learned

Async is not scary if you keep the shape of your messages small and your contracts clear. Queues for work, topics for events, persistent for safety, idempotent for peace. Wrap each consumer with a retry policy and a dead letter queue. Keep XA rare. Watch your queues just like you watch CPU and memory. With that base, JMS turns into a pressure valve for your app. The web tier stays quick, the workers chew through the backlog, and the business sleeps better.

Right now the tools are good enough and the API is stable. ActiveMQ is friendly for local runs. The big vendors ship mature brokers. Spring keeps the code light. You do not need a big rewrite to get the benefits. Start with one slow step in your app, push it to a queue, and measure. Make it boring and you will get async without fear.

Software Architecture Software Engineering

Post navigation

Previous post
Next post
  • Digital Experience (94)
    • Experience Strategy (19)
    • Experience-Driven Commerce (5)
    • Multi-Channel Experience (9)
    • Personalization & Targeting (21)
    • SEO & Performance (10)
  • Marketing Technologies (92)
    • Analytics & Measurement (14)
    • Content Management Systems (45)
    • Customer Data Platforms (4)
    • Digital Asset Management (8)
    • Marketing Automation (6)
    • MarTech Stack & Strategy (10)
    • Technology Buying & ROI (3)
  • Software Engineering (310)
    • Business of Software (20)
    • Code (30)
    • Development Practices (52)
    • Digital Transformation (21)
    • Engineering Management (25)
    • General Software (82)
    • Productivity & Workflow (30)
    • Software Architecture (85)
    • Technical Implementation (23)
  • 2025 (12)
  • 2024 (8)
  • 2023 (18)
  • 2022 (13)
  • 2021 (3)
  • 2020 (8)
  • 2019 (8)
  • 2018 (23)
  • 2017 (17)
  • 2016 (40)
  • 2015 (37)
  • 2014 (25)
  • 2013 (28)
  • 2012 (24)
  • 2011 (30)
  • 2010 (42)
  • 2009 (25)
  • 2008 (13)
  • 2007 (33)
  • 2006 (26)

Ab Testing Adobe Adobe Analytics Adobe Target AEM agile-methodologies Analytics architecture-patterns CDP CMS coding-practices content-marketing Content Supply Chain Conversion Optimization Core Web Vitals customer-education Customer Data Platform Customer Experience Customer Journey DAM Data Layer Data Unification documentation DXP Individualization java Martech metrics mobile-development Mobile First Multichannel Omnichannel Personalization product-strategy project-management Responsive Design Search Engine Optimization Segmentation seo spring Targeting Tracking user-experience User Journey web-development

©2025 CMO & CTO | WordPress Theme by SuperbThemes