Skip to content
Spring Boot sb core 4 min read

Autowiring & Qualifiers

Autowiring is how Spring resolves and supplies a bean’s dependencies automatically. You declare what a class needs and the container finds a matching bean to inject. This page covers the three injection styles, how to disambiguate when several beans match, and how to inject collections of beans — building on the fundamentals in Dependency Injection & IoC.

How autowiring resolves beans

When Spring needs to satisfy a dependency it looks for a bean of the required type. If exactly one matches, it injects it. If none match it fails (unless the dependency is optional); if several match, it needs a tiebreaker — covered below.

The @Autowired annotation marks an injection point. It can sit on a constructor, a field, or a setter.

The three injection styles

Stylefinal fieldsTestabilityRecommended?
ConstructorYesEasy — new with mocksYes
SetterNoModerateFor optional deps
FieldNoHard — needs reflectionNo

Dependencies arrive through the constructor, allowing final fields and guaranteeing the object is fully initialized. Since Spring 4.3, @Autowired is optional when there is a single constructor.

@Service
public class NotificationService {

    private final EmailSender emailSender;
    private final AuditLog auditLog;

    // No @Autowired needed — single constructor
    public NotificationService(EmailSender emailSender, AuditLog auditLog) {
        this.emailSender = emailSender;
        this.auditLog = auditLog;
    }
}

Setter injection

Useful for genuinely optional or reconfigurable dependencies.

@Service
public class ReportService {

    private MetricsClient metricsClient;

    @Autowired(required = false)
    public void setMetricsClient(MetricsClient metricsClient) {
        this.metricsClient = metricsClient;
    }
}

Field injection (discouraged)

@Service
public class LegacyService {
    @Autowired
    private EmailSender emailSender; // concise but hides dependencies
}

Warning: Field injection prevents final fields, hides what a class needs, and forces tests to use reflection or a full context. Prefer constructor injection in all production code.

Less boilerplate with Lombok

Lombok’s @RequiredArgsConstructor generates a constructor for all final fields, giving you constructor injection with no boilerplate. This is the most common idiom in modern Spring Boot codebases.

@Service
@RequiredArgsConstructor
public class NotificationService {

    private final EmailSender emailSender;
    private final AuditLog auditLog;
    // Lombok generates the constructor; Spring autowires through it
}

Resolving multiple candidates

When two or more beans satisfy the same type, Spring cannot choose on its own and throws NoUniqueBeanDefinitionException. Two annotations resolve the ambiguity.

Suppose you have two PaymentGateway implementations:

public interface PaymentGateway { void charge(long cents); }

@Component
public class StripeGateway implements PaymentGateway {
    public void charge(long cents) { /* ... */ }
}

@Component
public class PaypalGateway implements PaymentGateway {
    public void charge(long cents) { /* ... */ }
}

@Primary — a default winner

Mark one bean @Primary and it wins whenever no qualifier is given:

@Component
@Primary
public class StripeGateway implements PaymentGateway { /* ... */ }
@Service
public class CheckoutService {
    private final PaymentGateway gateway; // gets StripeGateway

    public CheckoutService(PaymentGateway gateway) {
        this.gateway = gateway;
    }
}

@Qualifier — pick by name

@Qualifier selects a specific bean by name, overriding @Primary at that injection point. The default bean name is the class name with a lowercase first letter (paypalGateway).

@Service
public class RefundService {

    private final PaymentGateway gateway;

    public RefundService(@Qualifier("paypalGateway") PaymentGateway gateway) {
        this.gateway = gateway;
    }
}

Tip: For clarity, define your own qualifier name on the bean: @Component @Qualifier("paypal"). Then inject with the same @Qualifier("paypal") so the wiring does not depend on class names.

Injecting collections of beans

Spring can inject all beans of a type as a List or Map. This is the idiomatic way to implement a plugin/strategy pattern.

@Service
public class PaymentDispatcher {

    private final List<PaymentGateway> gateways;          // every PaymentGateway bean
    private final Map<String, PaymentGateway> byName;     // key = bean name

    public PaymentDispatcher(List<PaymentGateway> gateways,
                             Map<String, PaymentGateway> byName) {
        this.gateways = gateways;
        this.byName = byName;
    }
}

The List contains every matching bean (ordered by @Order or Ordered if present); the Map keys are the bean names. This lets you add a new gateway simply by registering a new bean — no dispatcher changes needed.

Optional dependencies

When a dependency may legitimately be absent, express it explicitly rather than letting startup fail. Spring supports three forms:

@Service
public class FeatureService {

    private final Optional<AnalyticsClient> analytics;       // empty if no bean
    private final ObjectProvider<CacheClient> cacheProvider; // lazy, multi-aware

    public FeatureService(Optional<AnalyticsClient> analytics,
                          ObjectProvider<CacheClient> cacheProvider) {
        this.analytics = analytics;
        this.cacheProvider = cacheProvider;
    }

    public void run() {
        analytics.ifPresent(AnalyticsClient::track);
    }
}

Using Optional makes the absence explicit and forces callers to handle the empty case — see Optional for idiomatic usage. @Autowired(required = false) and @Nullable are alternatives, and ObjectProvider adds lazy resolution and uniqueness helpers.

Note: Constructor parameters are required by default. Wrapping a parameter in Optional (or @Nullable) is what tells Spring it may be missing.

Best Practices

  • Default to constructor injection with final fields; use Lombok @RequiredArgsConstructor to remove boilerplate.
  • Reserve setter injection for optional, reconfigurable dependencies.
  • Disambiguate multiple candidates with @Primary for the common default and @Qualifier for the exception.
  • Inject List/Map of an interface to build extensible strategy patterns.
  • Model truly optional dependencies with Optional or ObjectProvider instead of failing at startup.
Last updated June 13, 2026
Was this helpful?