Skip to content
JSBlogs
Go back

JmsClient in Spring Boot 4.x for cleaner messaging code

If you have worked with JMS in Spring, you know JmsTemplate. It got the job done, but sending a message — even a simple one — required callbacks, session handling, and boilerplate that felt out of place next to modern Spring code. Spring Boot 4.x addresses this by adding auto-configuration for JmsClient, a cleaner alternative with a fluent, builder-style API.

Table of contents

Open Table of contents

The problem with older versions (Spring Boot 3.x and earlier)

JmsTemplate has been in Spring for a long time. It made JMS accessible without managing connections directly, but working with it had friction that accumulated over time.

Sending a message meant callbacks inside callbacks

To send a message with custom properties or headers, you had to use a MessageCreator lambda that took a Session and returned a Message. This forced messaging logic into a nested structure.

jmsTemplate.send("orders", session -> {
    TextMessage message = session.createTextMessage(orderJson);
    message.setStringProperty("eventType", "ORDER_PLACED");
    message.setIntProperty("priority", 5);
    return message;
});

For a simple send with a couple of headers, that is a lot of plumbing.

No fluent way to configure per-message options

JmsTemplate was not designed for a fluent API. Options like delivery mode, time-to-live, and message priority required either setter configuration on the template itself (affecting all sends) or separate MessagePostProcessor arguments — neither of which was readable.

Reading selectively was awkward

Receiving a message matching a selector required going through receiveSelected() with a raw JMS selector string and no type-safe wrapper around the result.

Message message = jmsTemplate.receiveSelected("orders", "eventType = 'ORDER_PLACED'");
if (message instanceof TextMessage textMessage) {
    String body = textMessage.getText();
    // process body
}

Important: Using `JmsTemplate` for multiple different message types in the same service often leads to duplicated callback logic. This makes changes risky and review harder.

What Spring Boot 4.x changes

Spring Boot 4.x includes auto-configuration for JmsClient — a newer API from Spring Framework that brings a fluent, readable style to JMS operations. Think of it as RestClient for messaging.

Send a message without callbacks

The fluent API replaces the MessageCreator callback with a clear chain of method calls.

@Service
class OrderEventPublisher {

    private final JmsClient jmsClient;

    OrderEventPublisher(JmsClient jmsClient) {
        this.jmsClient = jmsClient;
    }

    void publish(String orderJson) {
        jmsClient.send()
                .destination("orders")
                .message(message -> message
                        .body(orderJson)
                        .property("eventType", "ORDER_PLACED")
                        .priority(5))
                .send();
    }
}

No session, no factory calls, no inner lambda that creates and returns a message type.

Receive with a selector and typed result

Reading messages is just as clean. You can apply a selector and convert the body in one chain.

Optional<String> nextOrder = jmsClient.receive()
        .destination("orders")
        .selector("eventType = 'ORDER_PLACED'")
        .message()
        .map(message -> {
            try {
                return ((TextMessage) message).getText();
            } catch (JMSException e) {
                throw new RuntimeException(e);
            }
        });

Tip: Use JmsClient for new messaging code and leave existing JmsTemplate usage in place until you have time to migrate. Both beans can live in the same application context.

Auto-configuration out of the box

Spring Boot 4.x auto-configures JmsClient if a ConnectionFactory bean is present — the same condition that triggers JmsTemplate auto-configuration. No extra setup is needed.

spring:
  activemq:
    broker-url: tcp://localhost:61616
    user: admin
    password: admin

That is all it takes. Boot configures the ConnectionFactory, and JmsClient is ready to inject.

Old way vs new way

AreaOlder versionsSpring Boot 4.x
Send styleMessageCreator callbackFluent builder chain
Per-message optionsSetters on template or post-processorsInline on the send chain
Receiving messagesreceiveSelected() with raw stringsFluent receive().selector().message()
Auto-configurationJmsTemplate onlyBoth JmsTemplate and JmsClient

Practical tips for rollout

  1. Introduce JmsClient in new message producers first before touching existing ones.
  2. Use the fluent chain for any send that sets more than one property — it pays for itself immediately.
  3. If you use @JmsListener, keep it as-is. JmsClient complements it, not replaces it.
  4. Add a simple integration test with an embedded broker to confirm messages reach the destination.

Caution: Do not use JmsClient for message listener configuration. Listeners should still be declared with @JmsListener and container factories. JmsClient is for sending and receiving on demand.

Migration checklist from older projects

  1. Check all JmsTemplate.send() calls that use MessageCreator callbacks.
  2. For each one, rewrite using jmsClient.send().destination(...).message(...).send().
  3. Replace jmsTemplate.receiveSelected() calls with jmsClient.receive().selector(...).message().
  4. Remove any MessagePostProcessor usage where it was only setting properties.
  5. Run integration tests against an embedded broker to confirm message content and headers.
  6. Leave @JmsListener methods untouched.

Note: JmsClient reduces boilerplate, but message schema design, dead-letter handling, and consumer group strategy still require deliberate thought. The client is just the entry point.

References


Share this post on:

Previous Post
Fine-grained Jackson configuration in Spring Boot 4.1
Next Post
RestTestClient in Spring Boot 4.x for cleaner HTTP tests