Skip to content

Implement comprehensive JMS ConnectionFactory configuration for Service Bus#47950

Merged
rujche merged 30 commits into
mainfrom
copilot/change-default-jms-connection-factory
Mar 6, 2026
Merged

Implement comprehensive JMS ConnectionFactory configuration for Service Bus#47950
rujche merged 30 commits into
mainfrom
copilot/change-default-jms-connection-factory

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 10, 2026

Plan: Change sender's default JmsConnectionFactory to CachingConnectionFactory

Understanding:

  • Currently, when spring.jms.servicebus.pool.enabled and spring.jms.cache.enabled are not set, both sender and listener container use ServiceBusJmsConnectionFactory
  • This is inefficient for senders using JmsTemplate as each send creates and closes a connection
  • The fix: Use CachingConnectionFactory by default for the sender side

Implementation Strategy:

  • Modify ServiceBusJmsConnectionFactoryConfiguration.java to implement complete configuration matrix
  • Modify ServiceBusJmsContainerConfiguration.java to match all scenarios
  • Add comprehensive test coverage for all 9 scenarios
  • Add JavaDoc tables to sender and listener container configurations
  • Update CHANGELOG.md with detailed configuration table and rules
  • Implement proper fallback logic when required classes not in classpath
  • All tests passing (77 JMS tests)
  • Fix checkstyle linting errors (operator wrapping)
  • Address all code review feedback:
    • Improved fallback logic to immediately return when requested classes missing
    • Memoized ALL dedicated listener container ConnectionFactory types to avoid duplicate instances
    • Enhanced fallback tests to validate real scenarios
    • Implemented lifecycle management with DisposableBean
    • Fixed CHANGELOG typo
    • Added comprehensive listener container ConnectionFactory tests verifying actual types
    • Extracted shared helper method to avoid code duplication
    • Improved integration test type assertion robustness
    • Removed hard references to optional dependencies to prevent class loading failures
    • Fixed compilation error with JmsProperties.Pool reference
    • Added logging to cleanup catch blocks for visibility
    • Added missing LOGGER field declaration
    • Fixed incorrect reflection class name (JmsPoolConnectionFactoryProperties)
    • Renamed all "receiver" terminology to "listenerContainer" throughout code, tests, and docs
Original prompt

This section details on the original issue you should resolve

<issue_title>[FEATURE REQ] Change sender's default JmsConnectionFactory from ServiceBusJmsConnectionFactory to CachingConnectionFactory</issue_title>
<issue_description>## Current behavior

Property (spring.jms.servicebus.pool.enabled and spring.jms.cache.enabled) not set or set to false Property (spring.jms.servicebus.pool.enabled or spring.jms.cache.enabled) set to true
sender side ServiceBusJmsConnectionFactory CachingConnectionFactory or JmsPoolConnectionFactory
receiver side ServiceBusJmsConnectionFactory CachingConnectionFactory or JmsPoolConnectionFactory

Expected behavior

Comment from @vinaysurya

If we don't use a CachingConnectionFactory on Sender side, I'm afraid when using JMSTemplate every send from Spring SDK will create and close a connection which will be very inefficient.

Property (spring.jms.servicebus.pool.enabled and spring.jms.cache.enabled) not set or set to false Property (spring.jms.servicebus.pool.enabled or spring.jms.cache.enabled) set to true
sender side CachingConnectionFactory CachingConnectionFactory or JmsPoolConnectionFactory
receiver side ServiceBusJmsConnectionFactory CachingConnectionFactory or JmsPoolConnectionFactory

Relate codes

  1. JmsListenerContainerFactory<?> jmsListenerContainerFactory(
    DefaultJmsListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory) {
    DefaultJmsListenerContainerFactory jmsListenerContainerFactory = new DefaultJmsListenerContainerFactory();
    configurer.configure(jmsListenerContainerFactory, connectionFactory);
    jmsListenerContainerFactory.setPubSubDomain(Boolean.FALSE);
    configureCommonListenerContainerFactory(jmsListenerContainerFactory);
    return jmsListenerContainerFactory;
    }
    @Bean
    @ConditionalOnMissingBean(name = "topicJmsListenerContainerFactory")
    JmsListenerContainerFactory<?> topicJmsListenerContainerFactory(
    DefaultJmsListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory) {
    DefaultJmsListenerContainerFactory jmsListenerContainerFactory = new DefaultJmsListenerContainerFactory();
    configurer.configure(jmsListenerContainerFactory, connectionFactory);
    jmsListenerContainerFactory.setPubSubDomain(Boolean.TRUE);
    configureCommonListenerContainerFactory(jmsListenerContainerFactory);
    configureTopicListenerContainerFactory(jmsListenerContainerFactory);
    return jmsListenerContainerFactory;
    }
  2. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
    BeanDefinitionRegistry registry) {
    BindResult<Boolean> poolEnabledResult = Binder.get(environment).bind("spring.jms.servicebus.pool.enabled", Boolean.class);
    BindResult<Boolean> cacheEnabledResult = Binder.get(environment).bind("spring.jms.cache.enabled", Boolean.class);
    if (isPoolConnectionFactoryClassPresent() && poolEnabledResult.orElseGet(() -> false)) {
    registerJmsPoolConnectionFactory(registry);
    return;
    }
    if (isCacheConnectionFactoryClassPresent() && cacheEnabledResult.orElseGet(() -> false)) {
    registerJmsCachingConnectionFactory(registry);
    return;
    }
    </issue_description>

Comments on the Issue (you are @copilot in this section)

@rujche ## Current status
  1. From azure-spring-boot-samples, the sender is JmsTemplate bean.
  2. The sender side, related code is in spring-boot/../JmsClientConfigurations.java, the JmsTemplate bean will only be configured when there is only one bean with type ConnectionFactory. @ConditionalOnSingleCandidate(ConnectionFactory.class).
  3. The receiver side, related code is in azure-sdk-for-java/.../ServiceBusJmsContainerConfiguration, it also uses the bean ConnectionFactory, it's the same one used in spring-boot/../JmsClientConfigurations.java

Implementation suggestion

  1. Keep the configuration with only one bean with type ConnectionFactory, to make the spring-boot/../JmsClientConfigurations.java can still work.
  2. On the sender side, in [azure-sdk-for-java/.../ServiceBusJmsConnectionFactoryConfiguration](https://github.com/Azure/azure-sdk-for-java/blob/be5923f94284df7f971c59785322a596fde1b37e/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/...

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 3 commits February 10, 2026 02:14
…iver ConnectionFactory

Co-authored-by: rujche <171773178+rujche@users.noreply.github.com>
Co-authored-by: rujche <171773178+rujche@users.noreply.github.com>
Co-authored-by: rujche <171773178+rujche@users.noreply.github.com>
Copilot AI changed the title [WIP] Change sender's default JmsConnectionFactory to CachingConnectionFactory Use CachingConnectionFactory by default for Service Bus JMS sender operations Feb 10, 2026
Copilot AI requested a review from rujche February 10, 2026 02:22
The receiver now conditionally uses the ConnectionFactory bean when it's
JmsPoolConnectionFactory or CachingConnectionFactory, otherwise creates a
dedicated ServiceBusJmsConnectionFactory. This matches the expected behavior
where the receiver should use the same pooled/cached connection factory as
the sender when those features are enabled.

Co-authored-by: rujche <171773178+rujche@users.noreply.github.com>
Copilot AI requested a review from rujche February 10, 2026 02:39
The receiver now only uses the shared ConnectionFactory bean when pooling
or caching is EXPLICITLY enabled. When properties are not set, the receiver
creates a dedicated ServiceBusJmsConnectionFactory instance, even though
the sender defaults to CachingConnectionFactory. This matches the expected
behavior table from the issue.

Co-authored-by: rujche <171773178+rujche@users.noreply.github.com>
Copilot AI requested a review from rujche February 10, 2026 02:51
@rujche
Copy link
Copy Markdown
Member

rujche commented Feb 10, 2026

Table

spring.jms.servicebus.pool.enabled value spring.jms.cache.enabled value sender receiver case number
not set not set CachingConnectionFactory ServiceBusJmsConnectionFactory 4
not set true CachingConnectionFactory CachingConnectionFactory 1
not set false ServiceBusJmsConnectionFactory ServiceBusJmsConnectionFactory 3
true not set JmsPoolConnectionFactory JmsPoolConnectionFactory 1
true true CachingConnectionFactory CachingConnectionFactory 2
true false JmsPoolConnectionFactory JmsPoolConnectionFactory 1
false not set CachingConnectionFactory ServiceBusJmsConnectionFactory 4
false true CachingConnectionFactory CachingConnectionFactory 1
false false ServiceBusJmsConnectionFactory ServiceBusJmsConnectionFactory 3

Explanation

  1. If only one of the properties (spring.jms.servicebus.pool.enabled and spring.jms.cache.enabled) set to true, set both sender and receiver to that factory. (4 cases)
  2. If both properties set to true, set both sender and receiver to CachingConnectionFactory. (1 case)
  3. Otherwise, if spring.jms.cache.enabled set to false, set both sender and receiver to ServiceBusJmsConnectionFactory . (2 case)
  4. Otherwise, use default value: set sender to CachingConnectionFactory, set receiver to ServiceBusJmsConnectionFactory. (2 cases)
  5. When use CachingConnectionFactory or JmsPoolConnectionFactory, confirm related class in the classpath. If it's not in the classpath, fallback to ServiceBusJmsConnectionFactory.

@copilot , According to this table and explanation:

  1. Update related code.
  2. Update test code, enrich test case.
  3. Update change log, add the table to change log to make it more clear.
  4. Add the table into related java doc to make it clear. In sender's java doc, only add sender in the table, in the receiver's java doc, only add receiver in the doc.

- Updated logic to follow the complete configuration table with all 9 scenarios
- Added JavaDoc tables to both sender and receiver configurations
- Added comprehensive test coverage for all scenarios
- Updated CHANGELOG with detailed configuration table and rules
- Both properties set to true now uses CachingConnectionFactory (cache takes precedence)
- Added proper fallback logic when required classes are not in classpath

Co-authored-by: rujche <171773178+rujche@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Feb 10, 2026

Table

| spring.jms.servicebus.pool.enabled value | spring.jms.cache.enabled value | sender | receiver | case number |
...

Done in commit 29cb8b3:

  1. Updated related code - Implemented the complete logic table with all 9 scenarios for both sender and receiver
  2. Enriched test cases - Added 7 new test cases covering all property combinations
  3. Updated CHANGELOG - Added the complete table with all scenarios and 5 rules explaining the behavior
  4. Added JavaDoc tables - Both ServiceBusJmsConnectionFactoryConfiguration (sender) and ServiceBusJmsContainerConfiguration (receiver) now have JavaDoc tables showing only their respective ConnectionFactory types

Key changes:

  • When both properties are true → CachingConnectionFactory (cache takes precedence)
  • When cache.enabled=false → checks pool.enabled for JmsPoolConnectionFactory vs ServiceBusJmsConnectionFactory
  • Proper fallback when required classes not in classpath
  • All 68 JMS tests passing

Copilot AI changed the title Use CachingConnectionFactory by default for Service Bus JMS sender operations Implement comprehensive JMS ConnectionFactory configuration for Service Bus Feb 10, 2026
@rujche
Copy link
Copy Markdown
Member

rujche commented Feb 10, 2026

@copilot Fix the lint error:

2026-02-10T08:59:38.1667963Z ##[section]Starting: Generate Linting Reports
2026-02-10T08:59:38.1673984Z ==============================================================================
2026-02-10T08:59:38.1674245Z Task         : PowerShell
2026-02-10T08:59:38.1674382Z Description  : Run a PowerShell script on Linux, macOS, or Windows
2026-02-10T08:59:38.1674517Z Version      : 2.268.1
2026-02-10T08:59:38.1674645Z Author       : Microsoft Corporation
2026-02-10T08:59:38.1674758Z Help         : https://docs.microsoft.com/azure/devops/pipelines/tasks/utility/powershell
2026-02-10T08:59:38.1674900Z ==============================================================================
2026-02-10T08:59:38.2973795Z Generating script.
2026-02-10T08:59:38.2987577Z ========================== Starting Command Output ===========================
2026-02-10T08:59:38.3000052Z [command]/usr/bin/pwsh -NoLogo -NoProfile -NonInteractive -Command . '/mnt/vss/_work/_temp/83345fb9-d951-4514-b781-8c89e93550e7.ps1'
2026-02-10T08:59:43.5087035Z 
2026-02-10T08:59:43.5148381Z File Name,Line Number,Column Number,Message,Type
2026-02-10T08:59:43.5149175Z "/mnt/vss/_work/1/s/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsContainerConfiguration.java",101,72,"'&&' should be on a new line.","com.puppycrawl.tools.checkstyle.checks.whitespace.OperatorWrapCheck"
2026-02-10T08:59:43.5150228Z "/mnt/vss/_work/1/s/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsContainerConfiguration.java",110,70,"'&&' should be on a new line.","com.puppycrawl.tools.checkstyle.checks.whitespace.OperatorWrapCheck"
2026-02-10T08:59:43.5151038Z "/mnt/vss/_work/1/s/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsContainerConfiguration.java",116,68,"'&&' should be on a new line.","com.puppycrawl.tools.checkstyle.checks.whitespace.OperatorWrapCheck"
2026-02-10T08:59:43.5151816Z "/mnt/vss/_work/1/s/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsContainerConfiguration.java",117,43,"'&&' should be on a new line.","com.puppycrawl.tools.checkstyle.checks.whitespace.OperatorWrapCheck"
2026-02-10T08:59:43.5152171Z 
2026-02-10T08:59:43.8298576Z Linting errors have been found. Navigate to https://dev.azure.com/azure-sdk/public/_build/results?buildId=5859617&view=artifacts&pathAsName=false&type=publishedArtifacts and download the 'linting-report.zip' artifact to see what was found.
2026-02-10T08:59:43.8316634Z �[32;1mCapacity�[0m�[32;1m MaxCapacity�[0m�[32;1m Length�[0m
2026-02-10T08:59:43.8317460Z �[32;1m--------�[0m �[32;1m-----------�[0m �[32;1m------�[0m
2026-02-10T08:59:43.8317947Z      568  2147483647    285
2026-02-10T08:59:43.8318350Z     1136  2147483647    570
2026-02-10T08:59:43.8318688Z     1136  2147483647    855
2026-02-10T08:59:43.8319051Z     2272  2147483647   1140
2026-02-10T08:59:43.8319377Z     2321  2147483647   1189
2026-02-10T08:59:43.8320027Z 
2026-02-10T08:59:43.8837982Z 
2026-02-10T08:59:43.8871651Z ##[error]PowerShell exited with code '1'.
2026-02-10T08:59:43.8920855Z ##[section]Finishing: Generate Linting Reports

Fixed OperatorWrapCheck violations by moving && operators from end of line
to the beginning of the next line, following the project's code style rules.

Co-authored-by: rujche <171773178+rujche@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Feb 10, 2026

@copilot Fix the lint error:

...

Fixed in commit 42d8270. Moved all && operators from the end of lines to the beginning of the next line to comply with OperatorWrapCheck. Checkstyle now passes with 0 violations.

@rujche
Copy link
Copy Markdown
Member

rujche commented Feb 13, 2026

@copilot

Fix this failure:


[ERROR] COMPILATION ERROR : 
[ERROR] /mnt/vss/_work/1/s/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsContainerConfiguration.java:[234,17] cannot find symbol
  symbol:   variable LOGGER
  location: class com.azure.spring.cloud.autoconfigure.implementation.jms.ServiceBusJmsContainerConfiguration
[ERROR] /mnt/vss/_work/1/s/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsContainerConfiguration.java:[243,17] cannot find symbol
  symbol:   variable LOGGER
  location: class com.azure.spring.cloud.autoconfigure.implementation.jms.ServiceBusJmsContainerConfiguration
[WARNING] Using legacy syntax for alternative repository. Use "id::file:///mnt/vss/_work/1/s/build" instead.
[WARNING] Using legacy syntax for alternative repository. Use "id::file:///mnt/vss/_work/1/s/build" instead.
##[error] /mnt/vss/_work/1/s/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsContainerConfiguration.java(234,17): error :  /mnt/vss/_work/1/s/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsContainerConfiguration.java:[234,17] cannot find symbol
##[error] /mnt/vss/_work/1/s/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsContainerConfiguration.java(243,17): error :  /mnt/vss/_work/1/s/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsContainerConfiguration.java:[243,17] cannot find symbol
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.14.1:compile (default-compile) on project spring-cloud-azure-autoconfigure: Compilation failure: Compilation failure: 
[ERROR] /mnt/vss/_work/1/s/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsContainerConfiguration.java:[234,17] cannot find symbol
[ERROR]   symbol:   variable LOGGER
[ERROR]   location: class com.azure.spring.cloud.autoconfigure.implementation.jms.ServiceBusJmsContainerConfiguration
[ERROR] /mnt/vss/_work/1/s/sdk/spring/spring-cloud-azure-autoconfigure/src/main/java/com/azure/spring/cloud/autoconfigure/implementation/jms/ServiceBusJmsContainerConfiguration.java:[243,17] cannot find symbol
[ERROR]   symbol:   variable LOGGER
[ERROR]   location: class com.azure.spring.cloud.autoconfigure.implementation.jms.ServiceBusJmsContainerConfiguration
[ERROR] -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
[ERROR] 
[ERROR] After correcting the problems, you can resume the build with the command
[ERROR]   mvn <args> -rf :spring-cloud-azure-autoconfigure

The process '/usr/bin/mvn' failed with exit code 1
Could not retrieve code analysis results - Maven run failed.
##[error]Build failed.
Finishing: Build and Package, JDK Version: 1.25

Added static Logger field and imported slf4j Logger and LoggerFactory to fix
compilation errors where LOGGER was referenced but not declared in the
ServiceBusJmsContainerConfiguration class.

Co-authored-by: rujche <171773178+rujche@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Feb 13, 2026

@copilot

Fix this failure:
...

Fixed in commit a81e2bb. Added the missing private static final Logger LOGGER field declaration and imported the required slf4j Logger and LoggerFactory classes.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

@rujche
Copy link
Copy Markdown
Member

rujche commented Feb 13, 2026

@copilot apply changes based on the comments in this thread

Changed Class.forName from "JmsProperties$Pool" to "JmsPoolConnectionFactoryProperties"
which is the correct type expected by JmsPoolConnectionFactoryFactory constructor.
This matches the usage in ServiceBusJmsConnectionFactoryConfiguration where
serviceBusJmsProperties.getPool() returns JmsPoolConnectionFactoryProperties.

Co-authored-by: rujche <171773178+rujche@users.noreply.github.com>
@rujche
Copy link
Copy Markdown
Member

rujche commented Feb 13, 2026

/azp run java - spring - tests

@rujche rujche requested a review from Copilot February 13, 2026 06:12
@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Changed all occurrences of "receiver" to "listenerContainer" for better
alignment with Spring JMS terminology. This affects:
- JavaDoc table headers
- Code comments
- Test descriptions
- CHANGELOG table column header

This makes the code more clear and consistent with Spring JMS naming conventions.

Co-authored-by: rujche <171773178+rujche@users.noreply.github.com>
Copilot AI and others added 2 commits February 24, 2026 01:45
Renamed method from getReceiverConnectionFactory to getConnectionFactory,
parameter from connectionFactory to beanConnectionFactory, and local variable
from receiverConnectionFactory to listenerConnectionFactory for better code clarity
and to avoid naming conflicts.

Co-authored-by: rujche <171773178+rujche@users.noreply.github.com>
Renamed all 4 test method names to use "listenerContainer" prefix instead of
"receiver" for consistency with Spring JMS terminology:
- receiverUsesDedicatedServiceBusConnectionFactoryByDefault
- receiverUsesCachingConnectionFactoryWhenExplicitlyEnabled
- receiverUsesPoolConnectionFactoryWhenExplicitlyEnabled
- receiverUsesDedicatedServiceBusConnectionFactoryWhenPoolDisabled

Co-authored-by: rujche <171773178+rujche@users.noreply.github.com>
@rujche
Copy link
Copy Markdown
Member

rujche commented Feb 24, 2026

/azp run java - spring - tests

@azure-pipelines
Copy link
Copy Markdown

Azure Pipelines successfully started running 1 pipeline(s).

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

@rujche
Copy link
Copy Markdown
Member

rujche commented Mar 6, 2026

/check-enforcer override

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

azure-spring All azure-spring related issues azure-spring-jms Issues related to Spring JMS. azure-spring-servicebus Spring service bus related issues.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[FEATURE REQ] Change sender's default JmsConnectionFactory from ServiceBusJmsConnectionFactory to CachingConnectionFactory

5 participants