Spring provides a JMS integration framework that simplifies the use of the JMS API much like Spring’s integration does for the JDBC API. The Spring Framework will take care of some low-level details when working with the JMS API.

The below tutorial illustrates how to build and run a Hello World example in which we will send/receive messages to/from Apache ActiveMQ using Spring JMS, Spring Boot and Maven.

General Project Setup

If you want to learn more about Spring JMS - head on over to the Spring JMS tutorials page.

Tools used:

  • ActiveMQ 5.14
  • Spring JMS 4.3
  • Spring Boot 1.5
  • Maven 3.5

We start by defining a Maven POM file which contains the dependencies for the needed Spring projects. The POM inherits from the spring-boot-starter-parent project and declares dependencies to spring-boot-starter-activemq and spring-boot-starter-test Spring Boot starters.

A dependency to activemq-junit is also added as we will include a basic unit test case that verifies our setup using an embedded ActiveMQ broker. The version of the dependency is specified in a property that needs to match with the ActiveMQ version supported by the spring-boot-starter-activemq starter. At the time of writing this was version '5.14.5'.

The spring-boot-maven-plugin Maven plugin is added so that we can build a single, runnable “uber-jar”, which is convenient to execute and transport our written code.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.codenotfound</groupId>
  <artifactId>spring-jms-activemq-helloworld</artifactId>
  <version>0.0.1-SNAPSHOT</version>

  <name>spring-jms-activemq-helloworld</name>
  <description>Spring JMS - ActiveMQ Consumer Producer Example</description>
  <url>https://www.codenotfound.com/2017/05/spring-jms-activemq-consumer-producer-example.html</url>

  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.3.RELEASE</version>
  </parent>

  <properties>
    <java.version>1.8</java.version>

    <activemq.version>5.14.5</activemq.version>
  </properties>

  <dependencies>
    <!-- spring-boot -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-activemq</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <!-- activemq -->
    <dependency>
      <groupId>org.apache.activemq.tooling</groupId>
      <artifactId>activemq-junit</artifactId>
      <version>${activemq.version}</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <!-- spring-boot-maven-plugin -->
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

Spring Boot is used in order to make a Spring JMS example application that you can “just run”. We start by creating an SpringJmsApplication which contains the main() method that uses Spring Boot’s SpringApplication.run() method to launch the application. The @SpringBootApplication annotation is a convenience annotation that adds: @Configuration, @EnableAutoConfiguration and @ComponentScan.

For more information on Spring Boot check out the Spring Boot getting started guide.

package com.codenotfound.jms;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringJmsApplication {

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

The below sections will detail how to create a sender and receiver together with their respective configurations. Note that it is also possible to have Spring Boot autoconfigure Spring JMS using default values so that actual code that needs to be written is reduced to a bare minimum.

Create a Spring JMS Message Producer

For sending messages we will be using the JmsTemplate which requires a reference to a ConnectionFactory and provides convenience methods which handles the creation and release of resources when sending or synchronously receiving messages.

Note that instances of the JmsTemplate class are thread-safe once configured.

In the below Sender class, the JmsTemplate is autowired as the actual creation of the Bean will be done in a separate SenderConfig class.

In this example we will use the convertAndSend() method which sends the given object to the specified destination, converting the object to a JMS message. The type of JMS message depends on the type of the object being passed. In the case of a String a JMS TextMessage will be created.

package com.codenotfound.jms.producer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;

public class Sender {

  private static final Logger LOGGER = LoggerFactory.getLogger(Sender.class);

  @Autowired
  private JmsTemplate jmsTemplate;

  public void send(String destination, String message) {
    LOGGER.info("sending message='{}' to destination='{}'", message, destination);
    jmsTemplate.convertAndSend(destination, message);
  }
}

The creation of the JmsTemplate and Sender is handled in the SenderConfig class. The class is annoted with @Configuration which indicates that the class can be used by the Spring IoC container as a source of bean definitions.

In order to be able to use the Spring JMS template we need to provide a reference to a ConnectionFactory which is used to create connections with the JMS provider. In addition it encapsulates various configuration parameters, many of which are vendor specific. In the case of ActiveMQ we use the ActiveMQConnectionFactory.

On the ActiveMQConnectionFactory we set the broker URL which is fetched from the application.yml properties file using the @Value annotation.

The JmsTemplate was originally designed to be used in combination with a J2EE container where the container would provide the necessary pooling of the JMS resources. As we are running this example on Spring Boot, we will wrap ActiveMQConnectionFactory using Spring’s CachingConnectionFactory in order to still have the benefit of caching of sessions, connections and producers as well as automatic connection recovery.

package com.codenotfound.jms.producer;

import org.apache.activemq.ActiveMQConnectionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.connection.CachingConnectionFactory;
import org.springframework.jms.core.JmsTemplate;

@Configuration
public class SenderConfig {

  @Value("${activemq.broker-url}")
  private String brokerUrl;

  @Bean
  public ActiveMQConnectionFactory activeMQConnectionFactory() {
    ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory();
    activeMQConnectionFactory.setBrokerURL(brokerUrl);

    return activeMQConnectionFactory;
  }

  @Bean
  public CachingConnectionFactory cachingConnectionFactory() {
    return new CachingConnectionFactory(activeMQConnectionFactory());
  }

  @Bean
  public JmsTemplate jmsTemplate() {
    return new JmsTemplate(cachingConnectionFactory());
  }

  @Bean
  public Sender sender() {
    return new Sender();
  }
}

Create a Spring JMS Message Consumer

Like with any messaging-based application, you need to create a receiver that will handle the messages that have been sent. The below Receiver is nothing more than a simple POJO that defines a method for receiving messages. In this example we named the method receive(), but you can name it anything you like.

The @JmsListener annotation creates a message listener container behind the scenes for each annotated method, using a JmsListenerContainerFactory. By default, a bean with name jmsListenerContainerFactory is expected that we will setup in the next section. Using the destination element, we specify the destination for this listener.

For testing convenience, we added a CountDownLatch. This allows the POJO to signal that a message is received. This is something you are not likely to implement in a production application.

package com.codenotfound.jms.consumer;

import java.util.concurrent.CountDownLatch;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jms.annotation.JmsListener;

public class Receiver {

  private static final Logger LOGGER = LoggerFactory.getLogger(Receiver.class);

  private CountDownLatch latch = new CountDownLatch(1);

  public CountDownLatch getLatch() {
    return latch;
  }

  @JmsListener(destination = "${queue.helloworld}")
  public void receive(String message) {
    LOGGER.info("received message='{}'", message);
    latch.countDown();
  }
}

The creation and configuration of the different Spring Beans needed for the Receiver POJO are grouped in the ReceiverConfig class. Note that we need to add the @EnableJms annotation to enable support for the @JmsListener annotation that was used on the Receiver.

The jmsListenerContainerFactory() is expected by the @JmsListener annotation from the Receiver. We set the concurrency between 3 and 10. This means that listener container will always hold on to the minimum number of consumers and will slowly scale up to the maximum number of consumers in case of increasing load.

Contrary to the JmsTemplate ideally don’t use Spring’s CachingConnectionFactory with a message listener container at all. Reason for this is that it is generally preferable to let the listener container itself handle appropriate caching within its lifecycle.

As we are connecting to ActiveMQ, an ActiveMQConnectionFactory is created and passed in the constructor of the CachingConnectionFactory.

package com.codenotfound.jms.consumer;

import org.apache.activemq.ActiveMQConnectionFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.config.DefaultJmsListenerContainerFactory;

@Configuration
@EnableJms
public class ReceiverConfig {

  @Value("${activemq.broker-url}")
  private String brokerUrl;

  @Bean
  public ActiveMQConnectionFactory activeMQConnectionFactory() {
    ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory();
    activeMQConnectionFactory.setBrokerURL(brokerUrl);

    return activeMQConnectionFactory;
  }

  @Bean
  public DefaultJmsListenerContainerFactory jmsListenerContainerFactory() {
    DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
    factory.setConnectionFactory(activeMQConnectionFactory());
    factory.setConcurrency("3-10");

    return factory;
  }

  @Bean
  public Receiver receiver() {
    return new Receiver();
  }
}

Testing the Spring JMS Template & Listener

In order to verify that we are able to send and receive a message to and from ActiveMQ, a basic SpringJmsApplicationTest test case is used. It contains a testReceive() unit test case that uses the Sender to send a message to the 'helloworld.q' queue on the ActiveMQ message broker. We then use the CountDownLatch from the Receiver to verify that a message was received.

An embedded ActiveMQ broker is automatically started by using an EmbeddedActiveMQBroker JUnit Rule.

Note that as the embedded broker gets shutdown once the unit test cases are finished, we need to stop our Sender and Receiver before this happens in order to avoid connection errors. This is done by calling a close() on the ApplicationContext using the @AfterClass annotation.

Below test case can also be executed after you install Apache ActiveMQ on your local system. You need to comment out the lines annotated with @ClassRule and @AfterClass to avoid the embedded broker gets created. In addition you need to change the 'activemq:broker-url' property to point to 'tcp://localhost:61616' in case you are the default URL value.

package com.codenotfound.jms;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.concurrent.TimeUnit;

import org.apache.activemq.junit.EmbeddedActiveMQBroker;
import org.junit.AfterClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.junit4.SpringRunner;

import com.codenotfound.jms.consumer.Receiver;
import com.codenotfound.jms.producer.Sender;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringJmsApplicationTest {

  private static ApplicationContext applicationContext;

  @Autowired
  void setContext(ApplicationContext applicationContext) {
    SpringJmsApplicationTest.applicationContext = applicationContext;
  }

  @AfterClass
  public static void afterClass() {
    ((ConfigurableApplicationContext) applicationContext).close();
  }

  @ClassRule
  public static EmbeddedActiveMQBroker broker = new EmbeddedActiveMQBroker();

  @Autowired
  private Sender sender;

  @Autowired
  private Receiver receiver;

  @Test
  public void testReceive() throws Exception {
    sender.send("helloworld.q", "Hello Spring JMS ActiveMQ!");

    receiver.getLatch().await(10000, TimeUnit.MILLISECONDS);
    assertThat(receiver.getLatch().getCount()).isEqualTo(0);
  }
}

In order to execute above example open a command prompt and run following Maven command:

mvn test

Maven will download the dependencies, compile the code and run the unit test case. The result should be a successful build as shown below:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.3.RELEASE)

21:26:38.419 [main] INFO  c.c.jms.SpringJmsApplicationTest - Starting SpringJmsApplicationTest on cnf-pc with PID 5124 (started by CodeNotFound in c:\code\st\spring-jms\spring-jms-activemq-helloworld)
21:26:38.419 [main] INFO  c.c.jms.SpringJmsApplicationTest - No active profile set, falling back to default profiles: default
21:26:39.187 [main] INFO  c.c.jms.SpringJmsApplicationTest - Started SpringJmsApplicationTest in 1.025 seconds (JVM running for 2.021)
21:26:39.233 [main] INFO  com.codenotfound.jms.producer.Sender - sending message='Hello Spring JMS ActiveMQ!' to destination='helloworld.q'
21:26:39.253 [DefaultMessageListenerContainer-2] INFO  c.codenotfound.jms.consumer.Receiver - received message='Hello Spring JMS ActiveMQ!'
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.541 sec - in com.codenotfound.jms.SpringJmsApplicationTest

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.915 s
[INFO] Finished at: 2017-05-04T21:26:40+02:00
[INFO] Final Memory: 17M/226M
[INFO] ------------------------------------------------------------------------

github mark If you would like to run the above code sample you can get the full source code here.

This concludes our example in which we used a Spring JMS template to create a producer and Spring JMS listener to create a consumer.

If you found this sample useful or have a question you would like to ask, leave a comment below!

Leave a Comment