Your first JMS application with Spring Boot

This tutorial will teach you how to create a Spring Boot JMS application which sends messages to ArtemisMQ JMS Server. At the end of it, you will learn how to send messages from a generic Spring Boot application and from a Spring Boot REST Controller. Let’s get started!

In order to start, we will create a project which uses artemis dependencies. We can use Spring Boot CLI for this purpose and execute:

$ spring init -d=artemis artemis -x

Also, as we will be using ArtemisMQ in embedded mode, we will also need to add the dependency “artemis-jms-server” to bootstrap the server:

<dependency>
	<groupId>org.apache.activemq</groupId>
	<artifactId>artemis-jms-server</artifactId>
</dependency>

Here is the final pom.xml file:

<?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>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.2.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.mastertheboss.springboot</groupId>
	<artifactId>SpringBootJMS</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>demo</name>
	<description>Demo project for Spring Boot</description>

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

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-artemis</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>org.apache.activemq</groupId>
			<artifactId>artemis-jms-server</artifactId>
		</dependency>
	</dependencies>

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

</project>

We need to configure one Queue that will be used for sending and receiving messages. We can do this into the application.properties file:

spring.artemis.mode=embedded
spring.artemis.embedded.enabled=true
spring.artemis.embedded.queues=springbootQueue
myqueue=springbootQueue

Within the configuration, we have declared the server to run in embedded mode, along with the Queue name to be “springbootQueue“. Now let’s look at the producer that will our main DemoApplication class:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.core.JmsTemplate;

@SpringBootApplication
public class DemoApplication {
	private static final Logger log = LoggerFactory.getLogger(DemoApplication.class);

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

	@Value("${myqueue}")
	String queue;

	@Bean
	CommandLineRunner start(JmsTemplate template) {
		return args -> {
			log.info("Sending> ...");

			template.convertAndSend(queue, "Hello World from Spring Boot!");
		};
	}
}

As we want to test message from the main application, we have defined a @Bean CommandLineRunner method. This method will therefore be executed after the Spring Boot finishes its pre-configuration. Also, this method uses the JmsTemplate instance, which will be autowired in the method automatically. The JmsTemplate uses the convenience convertAndSend method to send an Hello World message. To consume messages, we will be adding a Message Consumer Class:

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Consumer implements MessageListener{
	private Logger log = LoggerFactory.getLogger(Consumer.class);
	@Override
	public void onMessage(Message message) {
		try {
			log.info("Received message: " + message.getBody(Object.class));
		}catch (JMSException ex) {
			ex.printStackTrace();
		}
	}
}

Within this class, we have:

  • Implemented a MessageListener
  • Implemented the onMessage method to receive messages asynchronously.

Finally, we need to do plug into our project some extra configuration to connect to the ArtemisMQ server:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jms.listener.DefaultMessageListenerContainer;

import javax.jms.ConnectionFactory;

@Configuration
public class MessagingConfig {
	@Autowired
	private ConnectionFactory connectionFactory;
	@Value("${myqueue}")
	private String queue;

	@Bean
	public DefaultMessageListenerContainer messageListener() {
		DefaultMessageListenerContainer container = new DefaultMessageListenerContainer();
		container.setConnectionFactory(this.connectionFactory);
		container.setDestinationName(queue);
		container.setMessageListener(new Consumer());
		return container;
	}
}

The @Configuration annotation tells Spring to configure any declared methods annotated with the @Bean annotations.

  • We have @Autowired a ConnectionFactory for creating a connection with the default user identity to the broker. In this case it will create the connection to the ArtemisMQ server with the default credentials.
  • Finally, the @Bean messageListener defines a bean that will return a DefaultMessageListenerContainer instance. The DefaultMessageListenerContainer will be responsible for connecting to the queue and listening through the consumer’s MessageListener interface implementation.

That’s all. You can run the application with:

$ ./mvnw spring-boot:run

After running the program you should have the logs from the consumer and producer, something similar to this:

INFO 27897 --- [           main] com.example.artemis.DemoApplication      : Started DemoApplication in 2.624 seconds (JVM running for 3.183)
INFO 27897 --- [           main] com.example.artemis.DemoApplication      : Sending> ...
INFO 27897 --- [ssageListener-1] com.example.artemis.Consumer             : Received message: Hello World from Spring Boot!

Adding a Controller to send messages

We can make our example more dynamic by adding a REST Controller that will send a message received as PathVariable along with the request:

@RestController
public class RESTController {

    @Autowired private JmsTemplate jmsTemplate;

    @RequestMapping("/send/{message}")
    public String sendMessage(@PathVariable String message) {
        try {
            jmsTemplate.convertAndSend("springbootQueue",message);
            return "message sent!";
        } catch (Exception e) {
            e.printStackTrace();
            return "Error in sending message!";
        }


    }
}

That will require an extra dependency to run:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

You can now test the application, passing the message in the path. Example:

curl http://localhost:8080/send/john

Resulting in the log:

2019-12-19 19:03:02.696  INFO 18210 --- [ssageListener-1] c.m.springboot.SpringBootJMS.Consumer    : Received message: john

Sending Objects in the Message Payload

Provided that your objects are Serializable, they can be sent as payload in your JMS Messages. So assumed that you have the following Java POJO Class:

import java.io.Serializable;

public class Customer implements Serializable{
    String name;
    String surname;

    public Customer() {

    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

    @Override
    public String toString() {
        return "Customer{" +
                "name='" + name + '\'' +
                ", surname='" + surname + '\'' +
                '}';
    }
}

Now you can add a method to our Controller, which sends the Customer. The Customer needs to be sent in JSON format:

    @RequestMapping(path="/sendCustomer", method = RequestMethod.POST, consumes = {"application/json"})
    public void sendCustomer(@RequestBody Customer customer) {
        System.out.println("Sending customer "+customer);
        jmsTemplate.convertAndSend("springbootQueue", customer);
    }

For example, you can send it as follows:

curl -d '{"name":"john", "surname":"smith"}' -H "Content-Type: application/json" -X POST http://localhost:8080/sendCustomer

Resulting in the following log:

2019-12-19 19:00:17.798 INFO 17662 --- [ssageListener-1] c.m.springboot.SpringBootJMS.Consumer : Received message: Customer{name='john', surname='smith'}

Adding Connectors to the Embedded ArtemisMQ Server

In our example, Spring Boot Artemis starter creates a Configuration bean which will be used for EmbeddedJMS configuration. The Embedded JMS Server will only use an InVMAcceptorFactory, therefore it will not be available to remote clients. If you want to add extra Connectors configurations, you can implement the ArtemisConfigurationCustomizer interface and override the customize method, as in this example:

import org.apache.activemq.artemis.api.core.TransportConfiguration;
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyAcceptorFactory;
import org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnectorFactory;
import org.springframework.boot.autoconfigure.jms.artemis.ArtemisConfigurationCustomizer;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MessagingConfig implements ArtemisConfigurationCustomizer {
    @Override
    public void customize(org.apache.activemq.artemis.core.config.Configuration configuration) {
        configuration.addConnectorConfiguration("nettyConnector", new TransportConfiguration(NettyConnectorFactory.class.getName()));
        configuration.addAcceptorConfiguration(new TransportConfiguration(NettyAcceptorFactory.class.getName()));
    }
}

Source code for this tutorial: https://github.com/fmarchioni/mastertheboss/tree/master/spring/SpringBootJMS

Do you want some more Spring Boot stuff ? check Spring Boot Tutorials !

Found the article helpful? if so please follow us on Socials