Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Spring generic autowiring: preserve type arguments at the injection point

Last updated: 5 Oct 20264 min read
tutorial
IntermediateBy AITrove Editorial

Generic type arguments can distinguish candidate beans without a string qualifier when the bean signatures retain that type information.

A raw interface is ambiguous

Two receipt encoders implement the same Encoder interface but produce different output models. Asking for Encoder without its type argument leaves the container with multiple candidates. Asking for Encoder<PdfReceipt> can select the PDF bean when its declared type retains the parameter. This is a compile-time type relationship used by container metadata, not a runtime check that the returned PDF bytes are valid. Explicit qualifiers still help when two beans share the same generic type.

Keep factory signatures informative

A factory method that declares Object, a raw Encoder, or an overly broad base type can hide the parameter Spring needs to select a candidate. Declare Encoder<PdfReceipt> and Encoder<CsvReceipt> on the bean methods even if the implementations are concrete classes. Generic selection also applies to lists: List<Encoder<PdfReceipt>> should contain only matching candidates. Do not make a new marker interface for every representation when the generic contract already expresses the difference.

Prove the chosen bean

Refresh a minimal context containing both encoders and inject a coordinator that requires Encoder<PdfReceipt>. Assert the selected implementation and output type. Add a second PDF encoder and require the context to report ambiguity until a qualifier or primary policy resolves it. This catches accidental signature erasure during a refactor. Primary selection is a separate fallback and may hide the wrong candidate if used casually.

Implementation contract

Java
@Bean
Encoder<PdfReceipt> signedPdfEncoder(SigningKey key) {
    return new SignedPdfReceiptEncoder(key);
}

@Bean
Encoder<CsvReceipt> csvReceiptEncoder() {
    return new CsvReceiptEncoder();
}

final class ReceiptDownloadService {
    ReceiptDownloadService(Encoder<PdfReceipt> pdfEncoder) {
        this.pdfEncoder = pdfEncoder;
    }
    private final Encoder<PdfReceipt> pdfEncoder;
}

Cost and verification

Generic matching is resolved at wiring time, so request-path overhead is effectively unchanged. The cost is maintaining accurate method signatures and context tests as new implementations are registered.

Common Mistakes

  • Do not erase generic type arguments from @Bean return types.
  • Do not assume generics distinguish two candidates with the same parameterized type.
  • Do not use @Primary to conceal an unintended duplicate registration.

Read next

Spring qualifiers: select a collaborator when types are ambiguous, Spring @Primary and @Qualifier: default selection versus an explicit bean, Spring @Order on injected lists: execution order is not startup order, Spring @Component versus @Bean: choose who constructs the dependency, Spring constructor injection: required dependencies stay visible.

spring
spring-boot
spring-core
generic-type-autowire-selection
Storage details