Welcome to this in-depth guide on leveraging the embedded Open Liberty JMS engine for testing Jakarta EE 10 applications. Building on our previous tutorials, including the Jakarta EE Tutorial with Examples (#) and Advanced Jakarta EE Unit Testing (#), explores using the embedded Java Message Service (JMS) engine in Open Liberty to test message-driven applications.
We’ll extend the original project with a JMS-based messaging system, configure the embedded JMS engine, and enhance the Message-Driven Bean (MDB) to write messages to a text file. We’ll then verify the file’s contents in an integration test using JUnit 5.
Whether you’re a Java developer, enterprise architect, or DevOps professional, this guide will empower you to test JMS-based applications effectively in cloud-native Java environments. Let’s dive in!
Why Use the Embedded Open Liberty JMS Engine for Testing?
The Jakarta Messaging (JMS) API enables asynchronous communication in Jakarta EE 10 applications, ideal for microservices and event-driven systems. Open Liberty’s embedded JMS engine, provided by the wasJmsServer-1.0 feature, offers a lightweight messaging server for testing without an external broker like IBM MQ. This simplifies Jakarta EE testing and ensures reliability in enterprise Java development.
Benefits of the Embedded JMS Engine
The embedded JMS engine:
- Runs within Open Liberty, eliminating external dependencies.
- Supports queues and topics for flexible messaging.
- Enables fast, repeatable tests, crucial for cloud-native Java.
- Integrates with Jakarta EE components like MDBs and JAX-RS endpoints.
When to Use It
Use the embedded JMS engine for:
- Testing message production and consumption in REST APIs.
- Validating MDBs that process asynchronous messages.
- Simulating messaging workflows in a controlled environment.
Setting Up the Embedded JMS Engine in Your Jakarta EE Project
Let’s extend the Maven project from the original tutorial, adding JMS support and configuring the embedded JMS engine. We’ll assume you have the project set up in IntelliJ IDEA with UserResource and UserService.
Configuring Maven Dependencies
Update pom.xml to include JMS dependencies, JerseyTest, Mockito, and the Liberty Maven Plugin:
xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
<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.example</groupId> <artifactId>JakartaEETutorial</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <liberty.version>24.0.0.10</liberty.version> </properties> <dependencies> <dependency> <groupId>jakarta.platform</groupId> <artifactId>jakarta.jakartaee-api</artifactId> <version>10.0.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.10.3</version> <scope>test</scope> </dependency> <dependency> <groupId>org.g BLASSfish.jersey.test-framework</groupId> <artifactId>jersey-test-framework-core</artifactId> <version>3.1.9</version> <scope>test</scope> </dependency> <dependency> <groupId>org.glassfish.jersey.test-framework.providers</groupId> <artifactId>jersey-test-framework-provider-inmemory</artifactId> <version>3.1.9</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>5.14.2</version> <scope>test</scope> </dependency> </dependencies> <build> <finalName>JakartaEETutorial</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>3.4.0</version> </plugin> <plugin> <groupId>io.openliberty.tools</groupId> <artifactId>liberty-maven-plugin</artifactId> <version>3.10.3</version> <configuration> <serverName>defaultServer</serverName> <install> <runtimeArtifact> <groupId>io.openliberty</groupId> <artifactId>openliberty-jakartaee10</artifactId> <version>${liberty.version}</version> <type>zip</type> </runtimeArtifact> </install> </configuration> <executions> <execution> <id>install-liberty</id> <phase>prepare-package</phase> <goals> <goal>install-server</goal> </goals> </execution> <execution> <id>start-server</id> <phase>pre-integration-test</phase> <goals> <goal>start</goal> </goals> </execution> <execution> <id>stop-server</id> <phase>post-integration-test</phase> <goals> <goal>stop</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project> |
Configuring the Embedded JMS Engine
Update src/main/liberty/config/server.xml to enable the JMS engine and define a queue:
xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<server description="Open Liberty Server"> <featureManager> <feature>jakartaee-10.0</feature> <feature>wasJmsServer-1.0</feature> <feature>wasJmsClient-2.0</feature> <feature>mdb-3.2</feature> </featureManager> <httpEndpoint id="defaultHttpEndpoint" host="*" httpPort="9080" httpsPort="9443"/> <messagingEngine> <queue id="UserQueue"/> </messagingEngine> <jmsQueueConnectionFactory jndiName="jms/UserQueueConnectionFactory"> <properties.wasJms/> </jmsQueueConnectionFactory> <jmsQueue jndiName="jms/UserQueue" queueName="UserQueue"> <properties.wasJms/> </jmsQueue> <webApplication location="JakartaEETutorial.war" contextRoot="/"/> </server> |
This configuration enables the embedded JMS engine, defines UserQueue, and sets up JNDI resources for the queue and connection factory.
Affiliate Product Recommendation:
- IntelliJ IDEA Ultimate – Advanced support for JMS and Jakarta EE testing. Buy Now (#).
Implementing a JMS-Based Application
We’ll extend the project by adding a JAX-RS endpoint to send JMS messages and an MDB that writes messages to a text file.
Sending JMS Messages via JAX-RS
Update UserResource in src/main/java/com/example/UserResource.java to send a user creation event to the JMS queue:
java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
package com.example; import jakarta.inject.Inject; import jakarta.jms.ConnectionFactory; import jakarta.jms.JMSContext; import jakarta.jms.Queue; import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import javax.naming.InitialContext; import java.util.ArrayList; import java.util.List; @Path("/users") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class UserResource { private List<User> users = new ArrayList<>(); @Inject private UserService userService; @GET public List<User> getAllUsers() { return users; } @POST public User addUser(User user) throws Exception { users.add(user); <em>// Send JMS message</em> InitialContext ctx = new InitialContext(); ConnectionFactory cf = (ConnectionFactory) ctx.lookup("jms/UserQueueConnectionFactory"); Queue queue = (Queue) ctx.lookup("jms/UserQueue"); try (JMSContext jmsContext = cf.createContext()) { jmsContext.createProducer().send(queue, "Created user: " + user.getName()); } return user; } @GET @Path("/premium/{id}") public User getPremiumUser(@PathParam("id") Long id) { User user = userService.findUserById(id); user.setName("Premium " + user.getName()); return user; } } |
The addUser endpoint sends a text message to UserQueue with offloading the user creation event.
Consuming JMS Messages and Writing to a Text File
Create UserMessageConsumer in src/main/java/com/example/UserMessageConsumer.java to process messages and write them to a text file:
java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
package com.example; import jakarta.ejb.ActivationConfigProperty; import jakarta.ejb.MessageDriven; import jakarta.jms.Message; import jakarta.jms.MessageListener; import jakarta.jms.TextMessage; import java.io.BufferedWriter; import java.io.FileWriter; @MessageDriven(activationConfig = { @ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = "jms/UserQueue"), @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue") }) public class UserMessageConsumer implements MessageListener { private static final String OUTPUT_FILE = "target/messages.txt"; @Override public void onMessage(Message message) { try { if (message instanceof TextMessage) { String text = ((TextMessage) message).getText(); try (BufferedWriter writer = new BufferedWriter(new FileWriter(OUTPUT_FILE, true))) { writer.write(text); writer.newLine(); } } } catch (Exception e) { e.printStackTrace(); } } } |
The MDB appends each message to target/messages.txt, creating the file if it doesn’t exist.
Testing the JMS Application
We’ll write tests to validate message production and consumption, using JerseyTest for the JAX-RS endpoint and an integration test to read the text file’s contents.
Unit Testing the JAX-RS Endpoint with JerseyTest
Create a test in src/test/java/com/example/UserResourceJmsJerseyTest.java:
java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
package com.example; import jakarta.jms.ConnectionFactory; import jakarta.jms.JMSContext; import jakarta.jms.Queue; import jakarta.ws.rs.core.Application; import org.glassfish.hk2.utilities.binding.AbstractBinder; import org.glassfish.jersey.server.ResourceConfig; import org.glassfish.jersey.test.JerseyTest; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.*; public class UserResourceJmsJerseyTest extends JerseyTest { private ConnectionFactory mockConnectionFactory = mock(ConnectionFactory.class); private JMSContext mockJmsContext = mock(JMSContext.class); private jakarta.jms.MessageProducer mockProducer = mock(jakarta.jms.MessageProducer.class); @Override protected Application configure() { ResourceConfig config = new ResourceConfig(UserResource.class); config.register(new AbstractBinder() { @Override protected void configure() { bind(mockConnectionFactory).to(ConnectionFactory.class); bind(mockJmsContext).to(JMSContext.class); } }); return config; } @Test public void testAddUserSendsJmsMessage() throws Exception { when(mockConnectionFactory.createContext()).thenReturn(mockJmsContext); when(mockJmsContext.createProducer()).thenReturn(mockProducer); User user = new User(); user.setId(1L); user.setName("John Doe"); user.setEmail("john@example.com"); User response = target("/users").request().post(javax.ws.rs.client.Entity.json(user), User.class); assertEquals("John Doe", response.getName()); verify(mockProducer).send(any(Queue.class), eq("Created user: John Doe")); } } |
This test mocks JMS dependencies with Mockito and verifies that the addUser endpoint sends the correct message.
Integration Testing with the Embedded JMS Engine
Create an integration test in src/test/java/com/example/UserMessageConsumerIT.java to send a message and verify the text file’s contents:
java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
package com.example; import jakarta.jms.ConnectionFactory; import jakarta.jms.JMSContext; import jakarta.jms.Queue; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import javax.naming.InitialContext; import java.nio.file.Files; import java.nio.file.Paths; import static org.junit.jupiter.api.Assertions.assertTrue; public class UserMessageConsumerIT { private static final String OUTPUT_FILE = "target/messages.txt"; @BeforeEach public void setUp() throws Exception { <em>// Clear the output file before each test</em> Files.deleteIfExists(Paths.get(OUTPUT_FILE)); } @Test public void testMessageConsumption() throws Exception { <em>// Send a test message</em> InitialContext ctx = new InitialContext(); ConnectionFactory cf = (ConnectionFactory) ctx.lookup("jms/UserQueueConnectionFactory"); Queue queue = (Queue) ctx.lookup("jms/UserQueue"); try (JMSContext jmsContext = cf.createContext()) { jmsContext.createProducer().send(queue, "Test message"); } <em>// Wait for MDB to process</em> Thread.sleep(1000); <em>// Read the output file</em> String content = Files.readString(Paths.get(OUTPUT_FILE)); assertTrue(content.contains("Test message"), "Output file should contain 'Test message'"); } } |
This test:
- Clears messages.txt before each run to ensure isolation.
- Sends a test message to UserQueue.
- Waits briefly for the MDB to process the message.
- Reads messages.txt and verifies it contains the expected message.
Run the test with mvn verify to start Open Liberty (via the Liberty Maven Plugin) and execute the integration test. The test confirms that the MDB correctly writes messages to the file.
Affiliate Product Recommendation:
- Pluralsight Testing Course – Master JMS and integration testing for Jakarta EE. Enroll Now (#).
Best Practices for JMS Testing with Open Liberty
Adopt these practices to ensure effective Open Liberty JMS testing.
Mock JMS Dependencies for Unit Tests
Mock ConnectionFactory, JMSContext, and MessageProducer with Mockito in unit tests to keep them fast and isolated, as shown in UserResourceJmsJerseyTest. Avoid interacting with the JMS engine for unit tests.
Use the Embedded Engine for Integration Tests
Leverage the embedded JMS engine for integration tests to simulate real messaging scenarios. Configure minimal queues in server.xml to optimize test performance, as demonstrated in UserMessageConsumerIT.
Monitor Test Coverage
Add JaCoCo to pom.xml for test coverage:
xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.12</version> <executions> <execution> <goals> <goal>prepare-agent</goal> </goals> </execution> <execution> <id>report</id> <phase>test</phase> <goals> <goal>report</goal> </goals> </execution> </executions> </plugin> |
Run mvn test to generate reports in target/site/jacoco. Aim for high coverage to ensure robust Jakarta EE testing.
Affiliate Product Recommendation:
- New Relic Monitoring – Gain insights into application quality post-testing. Try Free (#).
Conclusion
This guide on the embedded Open Liberty JMS engine for testing has shown you how to build and test JMS-based Jakarta EE 10 applications. By extending the original project with a JMS-enabled JAX-RS endpoint and an MDB that writes to a text file, and verifying the output in an integration test, you’ve mastered Open Liberty JMS testing. The embedded JMS engine simplifies testing, making it ideal for cloud-native Java projects. Apply these techniques, explore the affiliate tools, and look out for future tutorials on microservices or advanced JMS configurations to deepen your enterprise Java development expertise!
We also have a Java 21 REST API step-by-step tutorial , that shows you beginner friendly Java REST API integration testing with Podman, with a complete end-to-end development workflow.