[MM’s] Boot Notes — Simplifying JMS in Spring Boot 4 with the new JmsClient API

How Spring Boot 4 Auto-Configures a Fluent, Chainable Alternative to JmsTemplate for Less Boilerplate

Simplifying JMS in Spring Boot 4 with the new JmsClient API

If you’ve worked with Spring’s JMS support over the years, you know the drill: JmsTemplate handled the core messaging tasks. It handled message creation and conversion but came with some friction — verbose methods, callback patterns and multiple ways to send the same message.

While JmsTemplate still works perfectly, it reflects an older era of Spring APIs. The arrival of JdbcClient and RestClient in Spring 6.1 demonstrated a new direction — a fluent, expressive way to perform common operations without so much boilerplate.

Now, with Spring Framework 7 (and Spring Boot 4), messaging joins the club. Meet JmsClient — the new fluent API for JMS.

⚡ TL;DR (Quick Recap)

  • Spring Framework 7 introduces JmsClient, a fluent alternative to JmsTemplate.
  • Provides chainable APIs, consistent exception handling and modern integration with Spring’s messaging abstractions.
  • Spring Boot 4 auto-configures JmsClient
  • Ideal for new projects looking for clean, fluent and test-friendly JMS messaging.

Introducing JmsClient: Fluent, Consistent, and Familiar

While JmsTemplate is the classic workhorse, JmsClient serves as a modern, fluent successor to JmsMessagingTemplate, which first introduced the MessagingException model and Message<?> integration back in Spring 4.1. JmsClient adopts these benefits into the new fluent builder pattern. JmsClient is the evolution of Spring’s JMS abstraction.

What’s New

  • Fluent API design — operations like send(), receive(), and withTimeToLive() chain naturally.
  • Unified exception model — throws MessagingException instead of JmsException.
  • Integration with spring-messaging — works seamlessly with Message<?> and MessageConverter.
  • Auto-configuration in Spring Boot 4 — just inject and use it.

Under the Hood:

  • JmsClient delegates to JmsTemplate for actual JMS operations
  • Provides a fluent facade over the proven JmsTemplate infrastructure
  • Reuses Spring’s existing message conversion and connection management.
  • No performance penalty — same underlying implementation, better API

Here’s a simple example:

client.destination("queue")
.withTimeToLive(5000)
.send("Hello, JMS from JMS Client!");

Clean, readable and aligned with the same design philosophy behind JdbcClient and RestClient.

From JmsTemplate to JmsClient — The Evolution

Let’s look at how message sending changes between the old and new APIs.

Traditional JmsTemplate

@Service
public class MessageService {
private final JmsTemplate jmsTemplate;
// ...
public void sendMessage(String destination, String message) {
jmsTemplate.convertAndSend(destination, message);
}
}

Modern JmsClient

@Service
public class DemoService {
private final JmsClient client;
private final DemoProperties properties;
// ...
public void sendMessage(Demo message) {
client.destination(properties.queue())
.send(message.message());
}
}

Same intent, less boilerplate. The JmsClient interface focuses on operation flow, not on callback mechanics or session management.

Key Differences at a Glance

API Style

  • JmsTemplate: Uses method overloading
  • JmsClient: Uses fluent chaining

Exception Model

  • JmsTemplate: Throws JmsException
  • JmsClient: Throws MessagingException

Message Building

  • JmsTemplate: Relies on callback patterns
  • JmsClient: Supports direct building or Message<?>

Configuration

  • JmsTemplate: Uses bean properties
  • JmsClient: Uses a builder pattern

Message Integration

  • JmsTemplate: Requires JmsMessagingTemplate for Message<?> support
  • JmsClient: Has built-in Message<?> support

Why It Matters

1. Fluent and Consistent API

No more juggling overloaded send methods or configuration setters. Everything flows naturally:

client.destination("demo.queue")
.withTimeToLive(5000)
.withHeaders(Map.of("id", "1"))
.send(order);

Each chained method describes message context — easy to read, easy to maintain.

2. Unified Exception Handling

All JmsClient operations throw MessagingException, aligning JMS with Spring’s other messaging technologies (AMQP, Kafka, WebSocket). No need to switch between exception hierarchies across modules.

3. Easy Testing in Spring Boot 4

The test uses an embedded broker and @MockitoSpyBean to verify message delivery end-to-end.

@SpringBootTest
class DemoServiceTest {
@Autowired
private DemoService service;
@MockitoSpyBean
private DemoConsumer consumer;
@Test
void testSendMessage() {
service.sendMessage(new Demo("Hello from JmsClient test!"));
verify(consumer, times(1)).receiveMessage("Hello from JmsClient test!");
}
}

Quick Setup: JMS in Spring Boot 4

Spring Boot 4 auto-configures JMS infrastructure. Example for both ActiveMQ Classic or Artemis.

Dependencies:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-artemis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-jakarta-server</artifactId>
</dependency>

Configuration:

spring.artemis.mode=embedded
spring.artemis.embedded.persistent=false
spring.artemis.embedded.queues=demo-queue
mm.demo.queue=demo-queue
logging.level.org.apache.activemq.audit=OFF

And you’re ready to use JmsClient:

@Component
public class Producer {
private final JmsClient client;
public Producer(JmsClient client) {
this.client = client;
}
public void send(String payload) {
client.destination("demo-queue").send(payload);
}
}

Spring Boot auto-configures everything — ConnectionFactory, MessageConverter and even the default listener factory for @JmsListener.

JmsClient integrates seamlessly with Spring’s MessageConverter infrastructure for automatic payload conversion. Spring Boot auto-configures JSON conversion when Jackson is on the classpath.

client.destination("demo-order")
.send(new Order("12345", "Order"));

// Receive and convert back to object
var order = client.destination("demo-order")
.withReceiveTimeout(2000)
.receive(Order.class);

Or you can customize the auto-configured JmsClient

@Bean
public MessageConverter jacksonJmsMessageConverter() {
MappingJackson2MessageConverter converter =
new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
return converter;
}

@Bean
public JmsClient customJmsClient(ConnectionFactory connectionFactory) {
return JmsClient.builder(connectionFactory)
.messageConverter(jacksonJmsMessageConverter())
.messagePostProcessor(auditingProcessor())
.build();
}

Advanced Usage Highlights

Receiving Messages

var message = client.destination("my-queue")
.withReceiveTimeout(2000)
.receive(String.class);

Custom Headers

client.destination("demo-queue")
.withHeaders(Map.of("tracking-id", "abc123"))
.send("Tracking record");

Sending Message Objects

var msg = MessageBuilder.withPayload("Hello")
.setHeader("priority", 5)
.build();
client.destination("priority-queue").send(msg);

Message Interception

Need to add headers to every outgoing message? Use a MessagePostProcessor:

var client = JmsClient.builder(connectionFactory)
.messagePostProcessor(m -> MessageBuilder.fromMessage(m)
.setHeader("id", "1")
.build())
.build();

Migration Guide: JmsTemplate → JmsClient

The transition path is intentionally simple.

Before:

jmsTemplate.convertAndSend("demo-queue", demo);

After:

client.destination("demo-queue").send(demo);

Current Limitations

While JmsClient covers most common use cases, some scenarios still require JmsTemplate:

  • Complex session management requiring direct Session access
  • Custom message creation beyond MessageConverter
  • Specific JMS provider features not exposed in the fluent API

You can even use both APIs side-by-side while modernizing gradually — Spring Framework 7 supports both.

When to Choose JmsClient

Use JmsClient when:

  • Starting a new Spring Boot 4 project
  • You prefer fluent, readable APIs
  • You need unified MessagingException semantics
  • You’re already familiar with JdbcClient or RestClient

Keep JmsTemplate when:

  • Maintaining large legacy systems
  • Needing low-level JMS features not yet exposed in JmsClient
  • Your team’s tooling or frameworks depend on JmsTemplate

Final Takeaways

Spring Framework 7’s JmsClient signals the future of messaging in the Spring ecosystem. By adopting a fluent design, unified exceptions, and Spring Boot 4 auto-configuration, JMS development feels once again modern and consistent with the rest of Spring’s client stack.

Key things to remember:

  • JmsClient — less boilerplate, more readability
  • Spring Boot 4 — simpler JMS setup, better testing
  • Migration — drop-in and incremental

If your next service involves message queues or event-driven processing, start with JmsClient — it’s the cleanest, most elegant way to handle JMS in Spring yet.

You can find all the code on GitHub.

Originally posted on marconak-matej.medium.com.