We have a Spring Boot app configured with @EnableKafka
and multiple instances of ConcurrentKafkaListenerContainerFactory
to consume from different topics that require different deserializers.
With this configuration application startup fails with message
Error creating bean with name 'kafkaListenerContainerFactory' defined in class path resource [org/springframework/boot/autoconfigure/kafka/KafkaAnnotationDrivenConfiguration.class]: Unsatisfied dependency expressed through method 'kafkaListenerContainerFactory' parameter 1; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.kafka.core.ConsumerFactory<java.lang.Object, java.lang.Object>' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
KafkaAutoConfiguration
attempts to create a ConcurrentKafkaListenerContainerFactory
bean due to the condition @ConditionalOnMissingBean(name = "kafkaListenerContainerFactory")
. Our configuration does not have a bean with this name hence the error.
Should the condition not check a bean exists with type ConcurrentKafkaListenerContainerFactory
rather than a specific bean name? It would then backoff in a scenario like ours where we have multiple beans of this type defined?
Comment From: wilkinsona
Thanks for the issue report. Can you please share a minimal example that reproduces the failure? Looking at the auto-configuration and your description, I cannot see why there's no bean of type ConsumerFactory<Object, Object>
. It should have been created by KafkaAutoConfiguration
:
https://github.com/spring-projects/spring-boot/blob/2c9cf6511efd8453b39e1c31d66996e8da9b1e33/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/kafka/KafkaAutoConfiguration.java#L82-L86
Comment From: manderson23
Please see the test case at https://gist.github.com/manderson23/bed77497cd556f24bf77d758ae781455
If fails with the error message I mentioned. If I remove the //
to exclude KafkaAutoConfiguration
the test runs.
Comment From: wilkinsona
Thank you. The definition of your own ConsumerFactory
beans was the missing piece. The auto-configured ConsumerFactory
backs off if there's any user-provided ConsumerFactory
bean in the context but the definition for kafkaListenerContainerFactory
expects to consume one that's specifically a ConsumerFactory<Object, Object>
. Yours are typed as ConsumerFactory<String, Integer>
and ConsumerFactory<Integer, String>
so the definition backs off and the consumption then fails.
Comment From: snicoll
I am not entirely sure I agree with that although there is clearly an issue. The current back-off arrangement is meant to make sure a default factory can be created automatically even if the user provides additional factories. Backing off when a bean of that type is present will remove that feature so we shouldn't do that.
The main problem I think is that we expect a ConsumerFactory
to be found with that specific signature and there is nothing that actually validates that. @ConditionalOnBean
doesn't allow us to specify the generic signature so one option would be to add a custom condition that does the relevant check.
Flagging for team attention to double check if I've missed something from Andy's comment.
Comment From: snicoll
@manderson23 renaming one of your two factories there to kafkaListenerContainerFactory
should be enough to workaround the problem for now. I guess none of those are really a "default" for you so it's a bit annoying.
Comment From: manderson23
@snicoll renaming if fine as a workaround but the surprising part to me was that a specific bean name for the factory was required for the auto-configuration to back-off. Requiring a specific bean name doesn't make sense when there are multiple factories and auto-configurations back off more elegantly.
Comment From: snicoll
The issue you’ve reported is legit and we’re going to fix it.
The point I was trying to make was that this feature uses a default factory with a specific bean and its absence from the context may lead to a failure unless you specify a custom factory for each endpoint.
The workaround consists at electing one of the two you have to be the default.
In this particular case, the bean name is a feature of the underlying library, not spring boot so it makes sense to require it.
Comment From: wilkinsona
I am not entirely sure I agree with that
Was there anything in particular that you didn't agree with? My comment was attempting to describe the current behaviour rather than suggesting a solution.
Comment From: snicoll
Was there anything in particular that you didn't agree with? My comment was attempting to describe the current behaviour rather than suggesting a solution.
Yes, I read your comment that way. I think I was disagreeing with "The definition of your own ConsumerFactory beans was the missing piece" but I don't see why now so please ignore me :-)
Comment From: wilkinsona
:-) That was just the missing piece for me to understand the reported behaviour.
Comment From: snicoll
We've discussed this and we think that a condition on the generic type can be avoided by creating the ConsumerFactory<Object,Object>
if none exists rather than failing the way we do know. We should double check that this particular ConsumerFactory
isn't used anywhere else though.