Intégration de JMS dans une application Spring Boot

Publié le 17/03/2026 Source : sfeir.dev

Dans le monde de l’entreprise, la communication asynchrone entre applications est essentielle pour garantir la résilience, la scalabilité et le découplage des systèmes. Les files de messages jouent ici un rôle primordial en permettant d’échanger des informations de manière fiable, même lorsque les applications ne sont pas disponibles simultanément.

L’API JMS (Java Message Service) est une spécification standard qui offre un cadre pour cette communication basée sur la messagerie. Dans cet article, nous allons découvrir JMS, ses avantages et ses limites, avant de voir comment l’intégrer concrètement dans une application Spring Boot avec ActiveMQ.

Présentation de JMS

MS est une API Java standardisée définissant un modèle de communication asynchrone entre producteurs et consommateurs de messages.
Deux modèles principaux sont proposés :

Grâce à ce standard, les développeurs peuvent utiliser différents brokers JMS (ActiveMQ, Artemis, IBM MQ…) sans changer leur code applicatif.

⚖️ Avantages et inconvénients

➕ Avantages

➖ Inconvénients

Exemple d’intégration avec Spring Boot et ActiveMQ

Dépendances Maven

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

Cette dépendance fournit tout le nécessaire pour intégrer ActiveMQ avec Spring JMS.

Propriétés de configuration

# Nom de la file JMS
app.jms.queue-name=message-queue

# URL et credentials du broker ActiveMQ
spring.activemq.broker-url=tcp://localhost:61616
spring.activemq.user=admin
spring.activemq.password=admin

# Nombre maximum de tentatives de redélivrance
app.jms.retry.max-redeliveries=3

Ces propriétés permettent de :

Classe de configuration JMS

@Configuration
@EnableJms
public class JmsConfig {
  @Value("${spring.activemq.broker-url}")
    private String brokerUrl;

    @Value("${spring.activemq.user}")
    private String user;

    @Value("${spring.activemq.password}")
    private String password;

    @Value("${app.jms.retry.max-redeliveries}")
    private int maxRedeliveries;

    ...
}

Les annotations :

Politique de redélivrance

@Bean
public RedeliveryPolicy redeliveryPolicy() {
    RedeliveryPolicy redeliveryPolicy = new RedeliveryPolicy();
    redeliveryPolicy.setMaximumRedeliveries(maxRedeliveries);
    redeliveryPolicy.setInitialRedeliveryDelay(1000); // 1s avant la 1ere tentative
    redeliveryPolicy.setUseExponentialBackOff(false);
    redeliveryPolicy.setRedeliveryDelay(2000); // 2s entre les tentatives suivantes
    return redeliveryPolicy;
}

Cette configuration précise à ActiveMQ comment réagir lorsqu’un message ne peut pas être traité correctement par le consommateur.

Pourquoi mettre une limite au nombre de rejeux ?

En pratique, cette limite agit comme un pare-feu de robustesse : elle protège l’application contre les scénarios où un message ne pourra jamais être consommé correctement.

Connexion au broker

@Bean
public ConnectionFactory connectionFactory(RedeliveryPolicy redeliveryPolicy){
    ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
    connectionFactory.setBrokerURL(brokerUrl);
    connectionFactory.setUserName(user);
    connectionFactory.setPassword(password);
    connectionFactory.setRedeliveryPolicy(redeliveryPolicy);
    connectionFactory.setTrustAllPackages(true);
    return connectionFactory;
}

Ce bean établit la connexion entre l’application et ActiveMQ, en appliquant la politique de rejeu.

Listener JMS

@Bean
public JmsListenerContainerFactory<?> jmsListenerContainerFactory(ConnectionFactory connectionFactory,
                                                                  DefaultJmsListenerContainerFactoryConfigurer configurer) {
    DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
    configurer.configure(factory, connectionFactory);
    factory.setSessionTransacted(true);
    return factory;
}

Ce bean crée le contenant des listeners JMS.

Template JMS avec MessageConverter

@Bean
public JmsTemplate jmsTemplate(ConnectionFactory connectionFactory, MessageConverter messageConverter) {
    JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
    jmsTemplate.setMessageConverter(messageConverter);
    return jmsTemplate;
}

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

Service d’envoi (Producteur)

@Service
public class MessageProducerService {

    private static final Logger log = LoggerFactory.getLogger(MessageProducerService.class);

    private final JmsTemplate jmsTemplate;

    @Value("${app.jms.queue-name}")
    private String queueName;

    public MessageProducerService(JmsTemplate jmsTemplate) {
        this.jmsTemplate = jmsTemplate;
    }

    public void sendMessage(ChatMessage chatMessage) {
        jmsTemplate.convertAndSend(queueName, chatMessage);
        log.info("Message envoyé dans la file : {}", chatMessage);
    }
}

Service de réception (Consommateur)

@Service
public class MessageConsumerService {

    private static final Logger log = LoggerFactory.getLogger(MessageConsumerService.class);

    @Value("${app.jms.retry.max-redeliveries}")
    private int maxRedeliveries;

    @JmsListener(destination = "${app.jms.queue-name}", containerFactory = "jmsListenerContainerFactory")
    public void receiveMessage(ChatMessage chatMessage, Message jmsMessage) throws JMSException {
        int deliveryCount = jmsMessage.getIntProperty("JMSXDeliveryCount");
        // Total attempts = 1 (initial) + maxRedeliveries
        int maxDeliveries = maxRedeliveries + 1;

        log.info("Message reçu (tentative {} sur {}): {}", deliveryCount, maxDeliveries, chatMessage);

        if (chatMessage.getContent().contains("ERROR")) {
            if (deliveryCount == maxDeliveries) {
                log.warn("Dernière tentative de rejeu ({} sur {}). Le message sera envoyé à la DLQ en cas d'échec.", deliveryCount, maxDeliveries);
            }
            log.error("Erreur simulée (tentative {} sur {}).", deliveryCount, maxDeliveries);
            throw new MessageProcessingException("Erreur simulée pour déclencher le rejeu.");
        }

        log.info("Message traité avec succès: {}", chatMessage);
    }
}

Dans le cadre de cet exemple, mon broker ActiveMQ est lancé dans un conteneur docker, ce dernier me permet d’avoir accès à une console d’administration à l’adresse suivante : http://localhost:8161/admin/index.jsp

Cette console permet de suivre les différentes queues utilisé ainsi que les message qui y transite, et grâce au message converter, je peux également voir le contenue de ces dernier.


Alternatives à JMS

Bien que JMS soit un standard robuste, d’autres solutions existent :

Le choix dépend du contexte : JMS reste pertinent dans des environnements Java historiques ou lorsqu’un broker JMS est déjà en place.

Conclusion

L’intégration de JMS dans une application Spring Boot permet de bénéficier d’une communication asynchrone fiable, reposant sur un standard éprouvé. Grâce à l’écosystème Spring, la mise en place est grandement simplifiée : quelques propriétés, une classe de configuration et des services annotés suffisent pour envoyer et consommer des messages.