ActiveMQ with Spring Boot: Queue and Topic Messaging Patterns

Starting and Managing ActiveMQ

To start the ActiveMQ broker, execute:

activemq start

To stop it:

activemq stop

Access the web console at http://localhost:8161 using credentials admin/admin.

Message Models: Queue vs Topic

Queue (Point-to-Point)

In a queue model, each message is consumed by exactly one consumer. Messages are persisted to disk (default: %ACTIVEMQ_HOME%/data/kahadb) until successfully processed. If no consumer is available when a message is sent, it remains queued. With multiple consumers, messages are distributed in a load-balanced manner — once consumed by one, they are removed from the queue.

Topic (Publish-Subscribe)

Topics broadcast messages to all active subscribers. Two subscription types exist:

  • Non-durable: Subscribers must be active to receive messages. Offline subscribers miss messages.
  • Durable: Subscribers retain subscriptions even when offline. Messages are stored until delivered upon reconnection.

By default, subscriptions are non-durable. Durable subscriptions behave similarly to individual persistent queues per subscriber.

Spring Boot Integration

Configuration Beans

Define JMS templates and listener containers for both queue and topic domains:

package com.example.activemq.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;
import org.springframework.jms.core.JmsTemplate;

import javax.jms.ConnectionFactory;

@Configuration
@EnableJms
public class JmsConfig {

    public static final String QUEUE_NAME = "app.queue.orders";
    public static final String TOPIC_NAME = "app.topic.notifications";

    @Bean("queueTemplate")
    public JmsTemplate queueTemplate(ConnectionFactory connectionFactory) {
        JmsTemplate template = new JmsTemplate(connectionFactory);
        template.setDefaultDestinationName(QUEUE_NAME);
        return template;
    }

    @Bean("queueListenerContainer")
    public DefaultJmsListenerContainerFactory queueContainerFactory(ConnectionFactory connectionFactory) {
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setSessionTransacted(true);
        factory.setConcurrency("1-3");
        factory.setRecoveryInterval(2000L);
        return factory;
    }

    @Bean("topicTemplate")
    public JmsTemplate topicTemplate(ConnectionFactory connectionFactory) {
        JmsTemplate template = new JmsTemplate(connectionFactory);
        template.setDefaultDestinationName(TOPIC_NAME);
        template.setPubSubDomain(true);
        return template;
    }

    @Bean("topicListenerContainer")
    public DefaultJmsListenerContainerFactory topicContainerFactory(ConnectionFactory connectionFactory) {
        DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setSessionTransacted(true);
        factory.setConcurrency("1-3");
        factory.setRecoveryInterval(2000L);
        factory.setPubSubDomain(true);
        return factory;
    }
}

Connection Factory Configuration

Configure broker connection via application.yml:

spring:
  activemq:
    broker-url: tcp://localhost:61616
    user: admin
    password: admin

Or programmatically:

@Bean
public ConnectionFactory connectionFactory() {
    return new ActiveMQConnectionFactory("admin", "admin", "tcp://localhost:61616");
}

Message Listeners

Queue lisetners — only one will receive each message:

package com.example.activemq.listener;

import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;

@Component
public class OrderQueueListener1 {

    @JmsListener(destination = "app.queue.orders", containerFactory = "queueListenerContainer")
    public void processOrder(String payload) {
        System.out.println("Listener 1 received: " + payload);
    }
}

@Component
public class OrderQueueListener2 {

    @JmsListener(destination = "app.queue.orders", containerFactory = "queueListenerContainer")
    public void processOrder(String payload) {
        System.out.println("Listener 2 received: " + payload);
    }
}

Topic listeners — all receive every message:

@Component
public class NotificationTopicListenerA {

    @JmsListener(destination = "app.topic.notifications", containerFactory = "topicListenerContainer")
    public void handleNotification(String payload) {
        System.out.println("Topic A: " + payload);
    }
}

@Component
public class NotificationTopicListenerB {

    @JmsListener(destination = "app.topic.notifications", containerFactory = "topicListenerContainer")
    public void handleNotification(String payload) {
        System.out.println("Topic B: " + payload);
    }
}

@Component
public class NotificationTopicListenerC {

    @JmsListener(destination = "app.topic.notifications", containerFactory = "topicListenerContainer")
    public void handleNotification(String payload) {
        System.out.println("Topic C: " + payload);
    }
}

Message Producers

Expose REST endpoints to send messages:

package com.example.activemq;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalTime;
import java.util.HashMap;
import java.util.Map;

@SpringBootApplication
@RestController
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Autowired
    @Qualifier("queueTemplate")
    private JmsTemplate queueTemplate;

    @Autowired
    @Qualifier("topicTemplate")
    private JmsTemplate topicTemplate;

    @GetMapping("/send/queue")
    public Map<String, String> sendToQueue() {
        String message = "Queue message at " + LocalTime.now();
        queueTemplate.convertAndSend(message);
        Map<String, String> response = new HashMap<>();
        response.put("sent", message);
        return response;
    }

    @GetMapping("/send/topic")
    public Map<String, String> sendToTopic() {
        String message = "Topic message at " + LocalTime.now();
        topicTemplate.convertAndSend(message);
        Map<String, String> response = new HashMap<>();
        response.put("sent", message);
        return response;
    }
}

Behavior Verification

When sending to /send/queue, only one of the two queue listeners logs the message — demonstrating exclusive consumption.

When sending to /send/topic, all three topic listeners receive and log the message simultaneously — confirming broadcast delivery.

Tags: ActiveMQ JMS SpringBoot MessageQueue PublishSubscribe

Posted on Tue, 02 Jun 2026 16:36:20 +0000 by *Lynette