@Qualifier

Picks a specific bean at an injection point by name or by a custom qualifier annotation when several beans share a type.

Signature
@Qualifier("beanName")
  • Added in Spring Boot Spring Framework 2.5

Parameters

ParameterTypeDescription
$valueoptionalString= ""The qualifier to match: a bean name, or a value given to a bean with its own @Qualifier.

Return value

n/aNarrows the candidates for an injection point to the bean whose qualifier value (or bean name) matches. Also usable as a meta-annotation for custom qualifier annotations.

Errors

No bean with a matching qualifier fails startup with NoSuchBeanDefinitionException.

@Qualifier chooses between beans of the same type at the point of injection. Put it on a constructor parameter, @Bean method parameter or field, and Spring injects only the bean whose qualifier matches. A bean's name always works as its qualifier, so @Qualifier("reportExecutor") finds the @Bean method reportExecutor() or the @Component("reportExecutor") class.

public ReportJob(@Qualifier("reportExecutor") ExecutorService executor) { ... }

On both sides

You can also put @Qualifier("fast") on the bean itself, which gives it a qualifier value separate from its name. Then renaming the class or method does not break injection points that use "fast".

Custom qualifier annotations

String qualifiers are easy to mistype, and a typo only shows up at startup. A custom annotation meta-annotated with @Qualifier, such as @Replica, is checked by the compiler, shows up in "find usages" and documents meaning rather than a name. Put it on the bean and on each injection point.

@Qualifier and @Primary together

A qualifier at the injection point always wins over @Primary. A common, clear set-up is one @Primary bean for the usual case and @Qualifier wherever the special one is needed.

Mistakes to avoid

  • Relying on the parameter name instead. Spring does fall back to matching the parameter name against bean names, but renaming a parameter then silently changes, or breaks, which bean is injected.
  • Putting @Qualifier on a constructor field in a Lombok-generated constructor, where it may not be copied to the parameter. Write the constructor yourself or configure Lombok to copy it.
  • Qualifying everything. If every injection point needs a qualifier, the beans probably deserve distinct types, such as ReportingDataSource wrappers, rather than a growing list of strings.

To get every bean of a type with its name, inject Map<String, PaymentGateway>. The keys are bean names, which is handy for picking an implementation from a request value without any if chain.

Example

Example · java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

@Configuration(proxyBeanMethods = false)
class ExecutorConfig {

    @Bean
    ExecutorService requestExecutor() {
        return Executors.newVirtualThreadPerTaskExecutor();
    }

    @Bean
    ExecutorService reportExecutor() {
        return Executors.newFixedThreadPool(2);
    }
}

@Component
public class MonthlyReportJob {

    private final ExecutorService executor;

    // Bean name as qualifier: picks the bounded pool, not the request executor.
    public MonthlyReportJob(@Qualifier("reportExecutor") ExecutorService executor) {
        this.executor = executor;
    }

    public void run() {
        executor.submit(() -> System.out.println("Building monthly report"));
    }
}

When to use it

  • Sending report generation to a small fixed thread pool while requests use virtual threads
  • Choosing express or standard shipping rates with a compiler-checked custom qualifier
  • Selecting an export format from a request parameter through a Map of beans

Frequently asked questions

Does @Qualifier use the bean name?

Yes. If no bean declares a matching @Qualifier value, Spring matches the value against bean names, so @Qualifier("reportExecutor") finds the bean called reportExecutor.

Which wins, @Qualifier or @Primary?

@Qualifier. It narrows the candidates first; @Primary only breaks ties among what is left.

More examples

A custom qualifier annotation

The compiler checks @Express, and an IDE finds every use of it. There is no string to mistype.

Example · java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
@interface Express {
}

interface ShippingRate {
    int pence(int grams);
}

@Component
class StandardRate implements ShippingRate {
    public int pence(int grams) { return 300 + grams / 10; }
}

@Component
@Express
class ExpressRate implements ShippingRate {
    public int pence(int grams) { return 900 + grams / 5; }
}

@Service
public class ShippingQuoteService {

    private final ShippingRate express;

    public ShippingQuoteService(@Express ShippingRate express) {
        this.express = express;
    }

    public int expressQuote(int grams) {
        return express.pence(grams);
    }
}

Picking an implementation by name at runtime

A GET /reports/42?format=pdf endpoint can pass the query value straight through; map the IllegalArgumentException for an unknown format to a 400 response rather than letting it become a 500. Supporting a new format is one new bean whose name is the format.

Example · java
import java.util.Map;

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

interface ExportFormat {
    byte[] render(Object report);
}

@Component("csv")
class CsvExport implements ExportFormat {
    public byte[] render(Object report) { return new byte[0]; }
}

@Component("pdf")
class PdfExport implements ExportFormat {
    public byte[] render(Object report) { return new byte[0]; }
}

@Service
public class ExportService {

    private final Map<String, ExportFormat> formats;

    // Keys are bean names: "csv" and "pdf".
    public ExportService(Map<String, ExportFormat> formats) {
        this.formats = formats;
    }

    public byte[] export(String format, Object report) {
        ExportFormat f = formats.get(format);
        if (f == null) {
            throw new IllegalArgumentException("Unsupported format: " + format);
        }
        return f.render(report);
    }
}

Qualifying a @Bean method parameter

@Qualifier works on @Bean method parameters exactly as on constructor parameters. Other beans asking for a plain HttpClient would still be ambiguous here, so in a real application one of the two would also be @Primary.

Example · java
import java.net.http.HttpClient;
import java.time.Duration;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

record PartnerApi(HttpClient client, String baseUrl) {
}

@Configuration(proxyBeanMethods = false)
public class PartnerConfig {

    @Bean
    HttpClient defaultHttpClient() {
        return HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(2)).build();
    }

    @Bean
    HttpClient slowPartnerHttpClient() {
        return HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(15)).build();
    }

    @Bean
    PartnerApi partnerApi(@Qualifier("slowPartnerHttpClient") HttpClient client) {
        return new PartnerApi(client, "https://partner.example/api");
    }
}

Discussion

  • Be the first to comment on this lesson.