spring-projects / spring-projects/spring-kafka

Lambda or method-reference Converter beans break @KafkaListener startup

Open
#4,591 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Java
Stars
2.5k
Forks
1.8k
Avg merge
21h 49m
Merged PRs (30d)
47

Description

In what version(s) of Spring for Apache Kafka are you seeing this issue?

4.0.6 (also present on main). Reproduced with Spring Boot 4.0.7 / Spring Framework 7.0.8 / Java 25.

Describe the bug

A Converter bean declared as a lambda or method reference makes application startup fail as soon as one @KafkaListener is present:

java.lang.IllegalArgumentException: Unable to determine source type <S> and target type <T> for your Converter [com.example.repro.ReproApplication$$Lambda/0x0000100001262c48]; does the class parameterize those types?
	at org.springframework.core.convert.support.GenericConversionService.addConverter(GenericConversionService.java:92)
	at org.springframework.kafka.annotation.KafkaListenerAnnotationBeanPostProcessor.addFormatters(KafkaListenerAnnotationBeanPostProcessor.java:1180)
	at org.springframework.kafka.annotation.KafkaListenerAnnotationBeanPostProcessor.afterSingletonsInstantiated(KafkaListenerAnnotationBeanPostProcessor.java:347)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1147)

KafkaListenerAnnotationBeanPostProcessor.addFormatters collects every Converter bean in the application context and passes each one to the single-argument FormatterRegistry.addConverter(Converter) overload:

private void addFormatters(FormatterRegistry registry) {
    for (Converter<?, ?> converter : getBeansOfType(Converter.class)) {
        registry.addConverter(converter);
    }
    ...
}

That overload resolves S and T reflectively from the converter's class. The compiler does not emit a generic signature for the synthetic class of a lambda or method reference, so resolution always fails (Spring Framework's position on this is spring-projects/spring-framework#22509 — a language limitation, not a framework bug).

The important part: the same bean is perfectly valid everywhere else in Spring Boot. ApplicationConversionService.addBeans wraps converter beans in a ConverterBeanAdapter built from the bean definition's ResolvableType (the @Bean method signature), which does carry the generics — see spring-projects/spring-boot#22885. So a lambda Converter bean works for MVC data binding, @ConfigurationProperties conversion, etc., and only breaks when @KafkaListener enters the picture. Nothing in the documentation suggests Converter beans must be named classes, and the failure surfaces in an unrelated part of the application — a shared library can ship such a bean and break only those consumers that happen to use Kafka.

To Reproduce

Two files, no broker needed — it fails during context refresh.

pom.xml (Boot 4.0.7 parent):

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-kafka</artifactId>
</dependency>
@SpringBootApplication
public class ReproApplication {

    public static void main(String[] args) {
        SpringApplication.run(ReproApplication.class, args);
    }

    // Accepted by Boot everywhere else; breaks @KafkaListener processing.
    @Bean
    public Converter<String, LocalDate> localDateConverter() {
        return LocalDate::parse;
    }

    @KafkaListener(topics = "some-topic", groupId = "repro")
    public void listen(String payload) {}
}

Startup fails with the stack trace above. Replacing the lambda with an anonymous inner class (new Converter<String, LocalDate>() { ... }) makes it start.

Expected behavior

@KafkaListener processing should tolerate lambda/method-reference Converter beans, the way the rest of Boot does — or, failing that, skip a converter it cannot introspect instead of aborting the whole context.

Suggested fix, mirroring spring-boot#22885: resolve the converter's generics from the bean definition's ResolvableType and register it through the GenericConverter route (ConverterBeanAdapter-style) rather than the reflection-based single-arg overload.

Workaround

Declare such beans as named or anonymous classes, or add a KafkaListenerConfigurer that supplies a custom MessageHandlerMethodFactory (which bypasses addFormatters entirely).

Sample

The two files above are the complete sample; happy to push it to a repository if that is preferred.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the two-file sample with the Boot 4.0.7 parent, then inspect KafkaListenerAnnotationBeanPostProcessor.addFormatters and its FormatterRegistry registration path. Compare it with Spring Boot's ApplicationConversionService.addBeans and ConverterBeanAdapter handling of bean-definition ResolvableType. Done means a lambda or method-reference Converter bean no longer aborts context startup when an @KafkaListener is present.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, kafka, spring
Domain
backend, distributed-systems
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.