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

Spring qualifiers: select a collaborator when types are ambiguous

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

A qualifier narrows the candidates for a dependency when more than one bean implements the requested type.

Download Spring source kit

This lesson uses the downloadable source kit: Java 21, Spring Boot 4.0.8 and its managed Spring Framework 7 dependencies. The version is pinned for repeatable builds.

Multiple exporters need a policy

The kit registers csv and archive exporters. An unqualified request for Exporter is ambiguous, and the test expects NoUniqueBeanDefinitionException. The report factory uses the archive qualifier, so the selection is explicit even if registration order changes.

The qualifier expresses wiring intent. It does not assign an application priority to arbitrary providers or prove that an exporter supports a requested format. A runtime format decision should use a capability registry with rejected unknown values rather than silently selecting the first bean.

Factory parameters keep wiring visible

QualifiedExportConfig uses proxyBeanMethods=false and obtains the selected exporter as a factory parameter. Calling another @Bean method directly in this mode is an ordinary method call and can create an unmanaged second object.

The archive report test checks behavior, while a separate assertion checks that type-only lookup remains ambiguous. Those are different facts. Testing only the final string could conceal a future factory change that happens to emit the same value.

Checked source

Java
package in.aitrove.learning;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.annotation.Qualifier;
@Configuration(proxyBeanMethods = false)
public class QualifiedExportConfig {
    @Bean("csv") ContainerContracts.Exporter csv() { return () -> "csv"; }
    @Bean("archive") ContainerContracts.Exporter archive() { return () -> "archive"; }
    @Bean ContainerContracts.ReceiptReport report(@Qualifier("archive") ContainerContracts.Exporter exporter) {
        return new ContainerContracts.ReceiptReport(exporter);
    }
}

Test the boundary

Run mvn test in the source-kit directory. CoreBoundaryTest checks the behavior described here. Java excerpts belong to the named source-kit classes; they are not independent source files unless the complete class is shown.

Costs and boundaries

Candidate resolution happens during wiring in this fixture. Rendering does not repeatedly search the context. A qualifier should not become a substitute for defining runtime dispatch cost and error handling.

Common Mistakes

  • Do not depend on registration order to break ambiguity.
  • A qualifier is not a format-validation contract.
  • Do not call @Bean factories as if proxyBeanMethods=false still intercepted the call.

Read next

Spring constructor injection: required dependencies stay visible, Auto configuration, Java ServiceLoader: provider configuration and lazy discovery.

Checked follow-up

Continue with Spring @Primary and @Qualifier: default selection versus an explicit bean.

spring
spring-boot
qualifiers
Storage details