How to Use Spring 7.0 JmsClient: The New Fluent JMS API
Purpose
This post demonstrates how to use Spring Framework 7.0’s new JmsClient fluent API for sending JMS messages.
When I worked with JMS in Spring applications, I always found JmsTemplate verbose. Sending a simple message required multiple lines of boilerplate code. I wished for something cleaner.
Spring 7.0 introduced JmsClient - a fluent API that makes JMS operations much simpler. Let me show you how to use it.
Environment
- Spring Framework 7.0
- Spring Boot 4.0
- Java 21
- ActiveMQ Artemis 2.38
- Maven 3.9
The Problem with JmsTemplate
Before Spring 7.0, I used JmsTemplate to send JMS messages. Here’s what the code looked like:
@Servicepublic class MessageSender {
@Autowired private JmsTemplate jmsTemplate;
public void sendMessage(String queueName, String message) { jmsTemplate.send(queueName, session -> { TextMessage textMessage = session.createTextMessage(message); textMessage.setJMSCorrelationID(UUID.randomUUID().toString()); textMessage.setJMSTimestamp(System.currentTimeMillis()); return textMessage; }); }}I found several problems with this approach:
- Verbose: Too much boilerplate for simple operations
- Callback hell: Nested lambdas for setting message properties
- Type confusion: Mix of JMS-specific types and Spring types
- QoS settings: Hard to configure quality of service settings inline
When I needed to set time-to-live or receive timeout, the code became even more complex.
The Solution: JmsClient
Spring 7.0 introduced JmsClient with a fluent API. Here’s the same operation:
@Servicepublic class MessageSender {
@Autowired private JmsClient jmsClient;
public void sendMessage(String queueName, String message) { jmsClient.destination(queueName) .withTimeToLive(60000) .send(message); }}You can see the difference. The fluent API chains destination selection with message sending in a single expression. I found this much easier to read and maintain.
Setting Up ActiveMQ Artemis
First, I set up ActiveMQ Artemis as the JMS broker. I used Docker for local development:
services: artemis: image: apache/activemq-artemis:2.38.0 ports: - "61616:61616" - "8161:8161" environment: ARTEMIS_USER: artemis ARTEMIS_PASSWORD: artemisRun it with:
docker-compose up -dThe Artemis console is available at http://localhost:8161 with username artemis and password artemis.
Spring Boot Configuration
I added the Artemis starter to my pom.xml:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-artemis</artifactId></dependency>Then I configured the connection in application.yml:
spring: artemis: mode: native broker-url: tcp://localhost:61616 user: artemis password: artemisSpring Boot auto-configures both JmsTemplate and JmsClient beans when the starter is on the classpath.
Injecting JmsClient
I injected the auto-configured JmsClient bean:
@Servicepublic class OrderService {
private final JmsClient jmsClient;
public OrderService(JmsClient jmsClient) { this.jmsClient = jmsClient; }
public void placeOrder(Order order) { jmsClient.destination("orders.queue") .send(order); }}I used constructor injection here because it makes testing easier and clearly shows dependencies.
Sending Different Payload Types
JmsClient handles different payload types automatically. I tried sending a POJO:
public record Order( String orderId, String customerId, List<OrderItem> items, BigDecimal totalAmount) {}
@Servicepublic class OrderService {
private final JmsClient jmsClient;
public void sendOrder(Order order) { // Send POJO directly - JmsClient converts to JSON jmsClient.destination("orders.queue") .withTimeToLive(300000) // 5 minutes .send(order); }}For POJO serialization to work, I added Jackson:
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId></dependency>And registered a message converter:
@Configurationpublic class JmsConfig {
@Bean public MessageConverter jacksonJmsMessageConverter() { MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); converter.setTargetType(MessageType.TEXT); converter.setTypeIdPropertyName("_type"); return converter; }}Sending with Custom Headers
I often need to add custom headers to messages. With JmsClient, I can pass a headers map:
public void sendOrderWithMetadata(Order order, String correlationId) { Map<String, Object> headers = new HashMap<>(); headers.put("X-Correlation-ID", correlationId); headers.put("X-Source", "order-service"); headers.put("X-Priority", "HIGH");
jmsClient.destination("orders.queue") .send(order, headers);}Or use Spring’s MessageBuilder:
import org.springframework.messaging.support.MessageBuilder;
public void sendOrderAsMessage(Order order, String correlationId) { Message<Order> message = MessageBuilder .withPayload(order) .setHeader("X-Correlation-ID", correlationId) .setHeader("X-Source", "order-service") .build();
jmsClient.destination("orders.queue") .send(message);}Receiving Messages
JmsClient also supports receiving messages with the same fluent style:
@Servicepublic class OrderProcessor {
private final JmsClient jmsClient;
public void processNextOrder() { Optional<Order> order = jmsClient.destination("orders.queue") .withReceiveTimeout(5000) // 5 seconds .receive(Order.class);
order.ifPresent(this::handleOrder); }
private void handleOrder(Order order) { // Process the order System.out.println("Processing order: " + order.orderId()); }}The receive() method returns an Optional containing the converted payload, or empty if no message was received within the timeout.
For receiving the full Spring Message object with headers:
public void processNextOrderWithHeaders() { Optional<Message<?>> message = jmsClient.destination("orders.queue") .withReceiveTimeout(5000) .receive();
message.ifPresent(msg -> { String correlationId = (String) msg.getHeaders().get("X-Correlation-ID"); Order order = (Order) msg.getPayload(); System.out.println("Correlation ID: " + correlationId); System.out.println("Order: " + order.orderId()); });}QoS Settings
I found the quality of service settings easy to configure inline:
public void sendWithQos(String payload) { jmsClient.destination("important.queue") .withTimeToLive(86400000) // 24 hours .withPriority(9) // High priority .withDeliveryMode(DeliveryMode.PERSISTENT) .send(payload);}These settings are applied to the operation without affecting other operations.
Common Mistakes I Made
Mistake 1: Forgetting Broker Configuration
When I first tried JmsClient, I got this error:
No qualifying bean of type 'org.springframework.jms.core.JmsClient' availableThe fix was adding the Artemis starter dependency and configuring the broker URL in application.yml.
Mistake 2: No MessageConverter for POJOs
When I sent a POJO without a message converter, I got:
MessageConversionException: Cannot convert object of type [Order] to JMS messageI fixed it by registering the MappingJackson2MessageConverter bean.
Mistake 3: Assuming Full JmsTemplate Feature Parity
I expected JmsClient to have all features of JmsTemplate. But JmsClient is focused on common operations. For advanced scenarios like browsing queues or executing callbacks with JMS sessions, I still need JmsTemplate.
When to Use JmsClient vs JmsTemplate
I use JmsClient when:
- Sending simple messages
- Receiving with timeout
- Need fluent, readable code
- Working with POJOs and JSON
I still use JmsTemplate when:
- Browsing queues
- Need JMS Session callbacks
- Using advanced JMS features
- Working with existing codebase
Summary
In this post, I showed how to use Spring Framework 7.0’s new JmsClient fluent API for JMS operations. The key points are:
- JmsClient provides a cleaner, chainable API compared to JmsTemplate
- Spring Boot auto-configures JmsClient when JMS starter is on classpath
- Use
destination("queue").send(payload)for sending messages - Use
destination("queue").receive(Type.class)for receiving messages - QoS settings like time-to-live and priority are configured inline
- Register a
MessageConverterfor POJO serialization
I found JmsClient makes my JMS code more readable and maintainable. The fluent API design fits well with modern Java coding style.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 Spring Framework 7.0 JmsClient API
- 👨💻 Spring Framework 7.0 Release Notes
- 👨💻 ActiveMQ Artemis Documentation
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments