Problem
ServiceBusJmsConnectionFactoryConfiguration controls the sender connection factory and ServiceBusJmsContainerConfiguration controls the listener connection factory, but both decision tables are driven by the same set
of properties (spring.jms.servicebus.pool.enabled / spring.jms.cache.enabled). Because the properties are shared, there is no combination that independently configures:
- Sender →
JmsPoolConnectionFactory
- Listener →
ServiceBusJmsConnectionFactory
Why this combination is required
Sender must use JmsPoolConnectionFactory
The Azure documentation for Spring JMS support (Connections) explicitly recommends using JmsPoolConnectionFactory
to handle the case where the broker drops idle connections and the producer receives The MessageProducer was closed due to an unrecoverable error.
JmsPoolConnectionFactory avoids this by proactively evicting connections (and their attached producers) on an idle timeout, before the broker drops them.
Listener must use ServiceBusJmsConnectionFactory
On Standard tier Azure Service Bus, topic subscriptions only work with ServiceBusJmsConnectionFactory as the listener connection factory. Other connection factories (CachingConnectionFactory,
JmsPoolConnectionFactory) require shared durable subscriptions, which are a Premium-tier-only feature (see #43279). Wrapping the listener factory breaks topic subscription for Standard tier users entirely.
We use Standard tier in development environments and Premium tier in production, but we need a single configuration that works across all environments without tier-specific overrides.
Current workaround
We work around this by setting spring.jms.cache.enabled=false to flip the sender decision table to a raw ServiceBusJmsConnectionFactory, then manually declaring a @Primary JmsPoolConnectionFactory bean that wraps it:
@Bean
@Primary
@ConditionalOnClass(JmsPoolConnectionFactory.class)
@ConditionalOnBooleanProperty(prefix = "spring.jms.cache", name = "enabled", havingValue = false)
public JmsPoolConnectionFactory senderConnectionFactory(ServiceBusJmsConnectionFactory rawConnectionFactory) {
JmsPoolConnectionFactory pool = new JmsPoolConnectionFactory();
pool.setConnectionFactory(rawConnectionFactory);
pool.setMaxConnections(1);
pool.setConnectionIdleTimeout(8 * 60 * 1000); // below broker's ~10 min idle disconnect
pool.setUseAnonymousProducers(false);
return pool;
}
This works but requires manual configuration and relies on an internal property (spring.jms.cache.enabled=false) whose side effects are not obvious.
Problem
ServiceBusJmsConnectionFactoryConfigurationcontrols the sender connection factory andServiceBusJmsContainerConfigurationcontrols the listener connection factory, but both decision tables are driven by the same setof properties (
spring.jms.servicebus.pool.enabled/spring.jms.cache.enabled). Because the properties are shared, there is no combination that independently configures:JmsPoolConnectionFactoryServiceBusJmsConnectionFactoryWhy this combination is required
Sender must use
JmsPoolConnectionFactoryThe Azure documentation for Spring JMS support (Connections) explicitly recommends using
JmsPoolConnectionFactoryto handle the case where the broker drops idle connections and the producer receives
The MessageProducer was closed due to an unrecoverable error.JmsPoolConnectionFactoryavoids this by proactively evicting connections (and their attached producers) on an idle timeout, before the broker drops them.Listener must use
ServiceBusJmsConnectionFactoryOn Standard tier Azure Service Bus, topic subscriptions only work with
ServiceBusJmsConnectionFactoryas the listener connection factory. Other connection factories (CachingConnectionFactory,JmsPoolConnectionFactory) require shared durable subscriptions, which are a Premium-tier-only feature (see #43279). Wrapping the listener factory breaks topic subscription for Standard tier users entirely.We use Standard tier in development environments and Premium tier in production, but we need a single configuration that works across all environments without tier-specific overrides.
Current workaround
We work around this by setting
spring.jms.cache.enabled=falseto flip the sender decision table to a rawServiceBusJmsConnectionFactory, then manually declaring a@Primary JmsPoolConnectionFactorybean that wraps it:This works but requires manual configuration and relies on an internal property (
spring.jms.cache.enabled=false) whose side effects are not obvious.