Spring Boot JMS


Java Message Service is an API for sending and receiving messages. It is an implementation to Producer-Consumer Design Pattern. This technique is usually implemented when you have a time consuming process and you need to avoid that a client is waiting for completing that process. To put this in context let’s think about a scenario where we could use it. The first thing that comes to my mind is an email delivery process. Sending an email consumes time and we can put email delivery as a message in a queue, so we can continue with our business flow without force to the client to wait until this email is deliver. In this example, we will see how to use JMS in a Spring Boot application. NOTE: If you need to know what tools you need to have installed in your computer in order to create a Spring Boot basic project, please refer my previous post: Spring Boot

Then execute this command in your terminal.

spring init --dependencies=webflux,activemq,lombok --language=java --build=gradle spring-boot-jms

This is the build.gradle file generated:

plugins {
  id 'org.springframework.boot' version '2.4.5'
  id 'io.spring.dependency-management' version '1.0.11.RELEASE'
  id 'java'
}

group = 'com.jos.dem.springboot.jms'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '15'

configurations {
  compileOnly {
    extendsFrom annotationProcessor
  }
}

repositories {
  mavenCentral()
}

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-activemq'
  implementation 'org.springframework.boot:spring-boot-starter-webflux'
  implementation 'org.apache.commons:commons-lang3'
  implementation 'org.apache.activemq:activemq-broker'
  compileOnly 'org.projectlombok:lombok'
  annotationProcessor 'org.projectlombok:lombok'
  testImplementation 'org.springframework.boot:spring-boot-starter-test'
  testImplementation 'io.projectreactor:reactor-test'
}

test {
  useJUnitPlatform()
}

Next add this dependencies:

implementation('org.apache.commons:commons-lang3')
implementation('org.apache.activemq:activemq-broker')

First, lets create a MessageService to deliver messages to the queue.

package com.jos.dem.springboot.jms.service;

import com.jos.dem.springboot.jms.command.Command;

public interface MessageService {

  void sendMessage(final Command command);
}

This is our MessageServiceImpl implementation class:

package com.jos.dem.springboot.jms.service.impl;

import com.jos.dem.springboot.jms.command.Command;
import com.jos.dem.springboot.jms.service.MessageService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Service;

import javax.jms.ObjectMessage;
import javax.jms.Session;

@Slf4j
@Service
@EnableJms
@RequiredArgsConstructor
public class MessageServiceImpl implements MessageService {

  private final JmsTemplate jmsTemplate;

  public void sendMessage(final Command command) {
    jmsTemplate.send(
        "destination",
        (Session session) -> {
          ObjectMessage message = session.createObjectMessage();
          message.setObject(command);
          return message;
        });
  }
}

Where:

  • @EnableJms Discovers methods annotated with @JmsListener.
  • JmsTemplate Sends messages to a JMS destination
  • Command Is a contract, so we can make serializable specific POJO.
package com.jos.dem.springboot.jms.command

import java.io.Serializable

interface Command extends Serializable {}

And this is the message object we are going to send.

package com.jos.dem.springboot.jms.command;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class PersonCommand implements Command {
  private String nickname;
  private String email;
}

Now we have all entities we need to set in order to send a message, next we need to specify the entities to receive and process messages

package com.jos.dem.springboot.jms.messengine;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.ObjectMessage;

@Slf4j
@Component
public class MessageListener {

  @JmsListener(destination = "destination", containerFactory = "myJmsContainerFactory")
  public void receiveMessage(Message message) throws JMSException {
    Object command = ((ObjectMessage) message).getObject();
    log.info("Message Received: " + ToStringBuilder.reflectionToString(command));
  }
}

As you can see JmsTemplate is sending a message to the destination and @JmsListener is waiting for new messages from destination, the another important part in this puzzle is the JMS container called myJmsContainerFactory which is defined in our Spring Boot application class as a bean.

package com.jos.dem.springboot.jms;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.jms.config.JmsListenerContainerFactory;
import org.springframework.jms.config.SimpleJmsListenerContainerFactory;

import javax.jms.ConnectionFactory;

@SpringBootApplication
public class JmsDemoApplication {

  @Bean
  public JmsListenerContainerFactory<?> myJmsContainerFactory(ConnectionFactory connectionFactory) {
    SimpleJmsListenerContainerFactory factory = new SimpleJmsListenerContainerFactory();
    factory.setConnectionFactory(connectionFactory);
    return factory;
  }

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

That’s it, now all components required have been already set. Here we are going to create a new Person message and send it to message service.

package com.jos.dem.springboot.jms.controller;

import com.jos.dem.springboot.jms.command.Command;
import com.jos.dem.springboot.jms.command.PersonCommand;
import com.jos.dem.springboot.jms.service.MessageService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;

@Slf4j
@RestController
@RequiredArgsConstructor
public class JmsController {

  private final MessageService messageService;

  @GetMapping("/")
  public Mono<String> index() {
    log.info("Sending message");
    Command person = new PersonCommand("josdem", "joseluis.delacruz@gmail.com");
    messageService.sendMessage(person);
    return Mono.just("Java Message Service");
  }
}

Now if you start our Spring Boot Application:

gradle bootRun

And hit this endopoint from command line:

curl http://localhost:8080/

You should be able to get this output:

MessageServiceImpl : Sending message
MessageListener    : Message Received <com.jos.dem.springboot.jms.command.PersonCommand@3a85a88c nickname=josdem email=joseluis.delacruz@gmail.com>

Using Maven

You can do the same using Maven, the only difference is that you need to specify --build=maven parameter in the spring init command line:

spring init --dependencies=webflux,activemq,lombok --language=java --build=maven spring-boot-jms

This is the pom.xml file generated:

<?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 https://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.4.5</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.jos.dem.springboot</groupId>
  <artifactId>spring-boot-jms</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>spring-boot-jms</name>
  <description>This project shows how to use JMS (Java Message Service) in a Spring Boot project</description>

  <properties>
    <java.version>15</java.version>
  </properties>

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

    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.activemq</groupId>
      <artifactId>activemq-broker</artifactId>
    </dependency>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.projectreactor</groupId>
      <artifactId>reactor-test</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <configuration>
          <excludes>
            <exclude>
              <groupId>org.projectlombok</groupId>
              <artifactId>lombok</artifactId>
            </exclude>
          </excludes>
        </configuration>
      </plugin>
    </plugins>
  </build>

</project>

To run the project with Gradle:

gradle bootRun

To run the project with Maven:

mvn spring-boot:run

To browse the project go here, to download the project:

git clone git@github.com:josdem/spring-boot-jms.git

Return to the main article