Skip to content

GH-4303: Allow @KafkaListener with no topic specification for programmatic resolution#4304

Merged
sobychacko merged 3 commits into
spring-projects:mainfrom
mcebanupgrade:gh-4303-fix-assertTopic-validation
Feb 26, 2026
Merged

GH-4303: Allow @KafkaListener with no topic specification for programmatic resolution#4304
sobychacko merged 3 commits into
spring-projects:mainfrom
mcebanupgrade:gh-4303-fix-assertTopic-validation

Conversation

@mcebanupgrade

Copy link
Copy Markdown
Contributor

Summary

The assertTopic validation introduced in GH-4170 / PR #4172 requires exactly one of topics/topicPartitions/topicPattern to be set (count == 1). This breaks a valid use case where a meta-annotated @KafkaListener intentionally omits all topic specifications because the custom KafkaListenerContainerFactory resolves topics programmatically in createListenerContainer().

This PR changes the validation from count == 1 to count <= 1 so that:

  • Multiple topic specifications (count > 1) are still rejected with IllegalStateException
  • No topic specification (count == 0) is permitted, allowing container factories to resolve topics at runtime

Use Case

A common pattern for custom annotation-driven listeners:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@KafkaListener(containerFactory = "myFactory")
@interface MyCustomKafkaListener {
}

With a custom container factory that resolves topics programmatically:

public class TopicResolvingContainerFactory extends ConcurrentKafkaListenerContainerFactory<String, Object> {

    @Override
    public ConcurrentMessageListenerContainer<String, Object> createListenerContainer(
            KafkaListenerEndpoint endpoint) {
        if (endpoint instanceof AbstractKafkaListenerEndpoint<?, ?> akle) {
            akle.setTopics("programmatically-resolved-topic");
        }
        return super.createListenerContainer(endpoint);
    }
}

Before GH-4170, this worked because no validation existed. After GH-4170, the count == 1 check rejects count == 0, throwing:

IllegalStateException: Only one of @Topic or @TopicPartition or @TopicPattern must be provided

Changes

  • KafkaListenerAnnotationBeanPostProcessor.java: Changed Assert.state(count == 1, ...) to Assert.state(count <= 1, ...)
  • KafkaListenerAnnotationBeanPostProcessorTests.java: Added two tests:
    • shouldRejectMultipleTopicSpecifications — verifies that specifying both topics and topicPattern still fails
    • shouldAllowNoTopicSpecificationForProgrammaticResolution — verifies that a meta-annotated @KafkaListener with no topics works when a custom container factory resolves topics programmatically

Fixes: #4303

@artembilan

Copy link
Copy Markdown
Member

Please, ensure DCO requirements.

@mcebanupgrade mcebanupgrade force-pushed the gh-4303-fix-assertTopic-validation branch from ac50b7a to f15b325 Compare February 19, 2026 21:04
@mcebanupgrade

Copy link
Copy Markdown
Contributor Author

@artembilan thanks! Ready for review now

@sobychacko

Copy link
Copy Markdown
Contributor

@mcebanupgrade Can you add your name as an author to the classes changed?


assertThatNoException().isThrownBy(ctx::refresh);
ctx.close();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The test verifies that no exception is thrown, but doesn't assert that the topic was actually resolved programmatically. Consider verifying via KafkaListenerEndpointRegistry that the container's topics contain "programmatically-resolved-topic".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@sobychacko thanks, test updated via b1fd2b0

…ion for programmatic resolution

The assertTopic validation introduced in spring-projectsGH-4170 requires exactly one of
topics/topicPartitions/topicPattern to be set (count == 1). This breaks
use cases where a meta-annotated @KafkaListener intentionally omits all
topic specifications because the custom KafkaListenerContainerFactory
resolves topics programmatically in createListenerContainer().

Change the validation from count == 1 to count <= 1 so that:
- Multiple topic specifications (count > 1) are still rejected
- No topic specification (count == 0) is permitted, allowing container
  factories to resolve topics at runtime

Fixes: spring-projects#4303
Signed-off-by: Maxim Ceban <mceban@upgrade.com>
…rt topic creation

Signed-off-by: Maxim Ceban <mceban@upgrade.com>
Signed-off-by: Maxim Ceban <mceban@upgrade.com>
@mcebanupgrade mcebanupgrade force-pushed the gh-4303-fix-assertTopic-validation branch from e2edef6 to c99c108 Compare February 26, 2026 16:39
@sobychacko sobychacko removed this from the 4.1.0-M2 milestone Feb 26, 2026
@sobychacko sobychacko merged commit d7b3315 into spring-projects:main Feb 26, 2026
3 checks passed
sobychacko pushed a commit that referenced this pull request Feb 26, 2026
…ammatic resolution (#4304)

Fixes #4303

The `assertTopic` validation introduced in GH-4170 requires exactly one of
`topics/topicPartitions/topicPattern` to be set `(count == 1)`. This breaks
use cases where a meta-annotated `@KafkaListener` intentionally omits all
topic specifications because the custom `KafkaListenerContainerFactory`
resolves topics programmatically in `createListenerContainer()`.

Change the validation from `count == 1` to `count <= 1` so that:
- Multiple topic specifications `(count > 1)` are still rejected
- No topic specification `(count == 0)` is permitted, allowing container
  factories to resolve topics at runtime

Update KafkaListenerAnnotationBeanPostProcessorTests to actually assert topic creation

**Auto-cherry-pick to `4.0.x` & `3.3.x`**

Signed-off-by: Maxim Ceban <mceban@upgrade.com>
spring-builds pushed a commit that referenced this pull request Feb 26, 2026
…ammatic resolution (#4304)

Fixes #4303

The `assertTopic` validation introduced in GH-4170 requires exactly one of
`topics/topicPartitions/topicPattern` to be set `(count == 1)`. This breaks
use cases where a meta-annotated `@KafkaListener` intentionally omits all
topic specifications because the custom `KafkaListenerContainerFactory`
resolves topics programmatically in `createListenerContainer()`.

Change the validation from `count == 1` to `count <= 1` so that:
- Multiple topic specifications `(count > 1)` are still rejected
- No topic specification `(count == 0)` is permitted, allowing container
  factories to resolve topics at runtime

Update KafkaListenerAnnotationBeanPostProcessorTests to actually assert topic creation

Signed-off-by: Maxim Ceban <mceban@upgrade.com>
(cherry picked from commit 87b4393)
@sobychacko

Copy link
Copy Markdown
Contributor

@mcebanupgrade Thanks for the PR! It has been merged upstream and back-ported to the relevant branches (See above for details).

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

assertTopic validation breaks meta-annotated @KafkaListener with programmatic topic resolution

3 participants