🍃 Section 5 · Question #26

What does the Autowired annotation do

By default, @Autowired treats all dependencies as mandatory (required = true). If no matching bean is found, Spring aborts startup with NoSuchBeanDefinitionException.


🟢 Junior Level

@Autowired is Spring’s primary annotation for Dependency Injection (DI). It instructs the Spring IoC container to automatically locate a matching bean in the ApplicationContext and inject it into the dependent component.

Where @Autowired Can Be Applied

  1. Constructors (Recommended & Best Practice):
    @Service
    public class OrderService {
        private final PaymentClient paymentClient;
    
        // Since Spring 4.3 / Spring Boot 1.4+, @Autowired is OPTIONAL on single constructors!
        public OrderService(PaymentClient paymentClient) {
            this.paymentClient = paymentClient;
        }
    }
    
  2. Setter Methods (Setter Injection):
    @Autowired
    public void setPaymentClient(PaymentClient paymentClient) {
        this.paymentClient = paymentClient;
    }
    
  3. Fields (Field Injection — Strongly Discouraged):
    @Autowired
    private PaymentClient paymentClient;
    

Optional Dependencies (required = false)

By default, @Autowired treats all dependencies as mandatory (required = true). If no matching bean is found, Spring aborts startup with NoSuchBeanDefinitionException.

To make a dependency optional, Spring provides three approaches:

// Approach 1: required flag (field remains null if missing)
@Autowired(required = false)
private AuditService auditService;

// Approach 2: Java 8 Optional
@Autowired
private Optional<AuditService> auditService;

// Approach 3: JSR-305 / Jakarta @Nullable
@Autowired
public void setAuditService(@Nullable AuditService auditService) {
    this.auditService = auditService;
}

🟡 Middle Level

The 5-Step Injection Resolution Algorithm

When Spring encounters an @Autowired injection point, AutowiredAnnotationBeanPostProcessor coordinates resolution across five deterministic steps:

                           [ @Autowired Injection Point ]
                                        │
                                        ▼
                     Step 1: Find all candidate beans by TYPE (byType)
                                        │
                      ┌─────────────────┴─────────────────┐
                      ▼                                   ▼
             [ Exactly 1 Found ]                [ Multiple Found ]
                      │                                   │
                      │                                   ▼
                      │                  Step 2: Filter by @Qualifier
                      │                                   │
                      │                         ┌─────────┴─────────┐
                      │                         ▼                   ▼
                      │                 [ Exactly 1 Match ] [ Still Multiple ]
                      │                         │                   │
                      │                         │                   ▼
                      │                         │       Step 3: Check for @Primary
                      │                         │                   │
                      │                         │         ┌─────────┴─────────┐
                      │                         │         ▼                   ▼
                      │                         │     [ Has @Primary ]    [ No @Primary ]
                      │                         │         │                   │
                      │                         │         │                   ▼
                      │                         │         │       Step 4: Check for @Priority
                      │                         │         │                   │
                      │                         │         │         ┌─────────┴─────────┐
                      │                         │         │         ▼                   ▼
                      │                         │         │     [ Has @Priority ]   [ No @Priority ]
                      │                         │         │         │                   │
                      │                         │         │         │                   ▼
                      │                         │         │         │     Step 5: Fallback by NAME (byName)
                      │                         │         │         │                   │
                      │                         │         │         │         ┌─────────┴─────────┐
                      │                         │         │         │         ▼                   ▼
                      │                         │         │         │   [ Name Matches ]    [ No Name Match ]
                      │                         │         │         │         │                   │
                      ▼                         ▼         ▼         ▼         ▼                   ▼
                [ INJECTION SUCCESSFUL ]                                     [ NoUniqueBeanDefinitionException ]
  1. Resolution byType: The container inspects DefaultListableBeanFactory for all beans assignable to the target type. If exactly one candidate exists, it is injected immediately.
  2. Filter via @Qualifier: If multiple candidates exist, Spring filters candidates matching the explicit @Qualifier("beanName") identifier.
  3. Filter via @Primary: If multiple candidates remain, Spring checks if one candidate is annotated with @Primary.
  4. Filter via @Priority: If no @Primary is present, Spring inspects the standard jakarta.annotation.Priority annotation (the candidate with the lowest numerical integer value wins).
  5. Fallback byName: If ambiguity persists, Spring compares the target parameter or field variable name against the bean names registered in the context.
  6. Failure: If none or more than one candidate remains, Spring throws NoUniqueBeanDefinitionException.

Injecting Collections and Maps

@Autowired can inject all implementations of an interface simultaneously:

@Service
public class PaymentRouter {

    // 1. Injects ALL beans implementing PaymentGateway:
    // Elements are automatically sorted if annotated with @Order or implementing Ordered!
    @Autowired
    private List<PaymentGateway> paymentGateways;

    // 2. Injects a Map where Key = Bean Name, Value = Bean Instance:
    @Autowired
    private Map<String, PaymentGateway> paymentGatewayMap;
}

🔴 Senior Level

Under the Hood: AutowiredAnnotationBeanPostProcessor

The resolution and injection of @Autowired is driven by AutowiredAnnotationBeanPostProcessor (AABPP):

  1. Metadata Collection Phase (MergedBeanDefinitionPostProcessor):
    • During postProcessMergedBeanDefinition, Spring reflects over the class hierarchy to discover fields and methods annotated with @Autowired, @Value, and jakarta.inject.Inject.
    • Discovered injection targets are cached inside an internal InjectionMetadata structure (composed of AutowiredFieldElement and AutowiredMethodElement descriptors). Caching eliminates redundant reflection when prototypes or multiple instances are instantiated.
  2. Property Population Phase (InstantiationAwareBeanPostProcessor):
    • During populateBean(), Spring calls postProcessProperties().
    • AABPP retrieves the cached InjectionMetadata and calls beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter).
    • Once resolved, Spring invokes field.setAccessible(true) and field.set(bean, dependency) via reflection.

Why @Autowired on static Fields Fails Silently

A frequent developer trap is attempting to inject dependencies into static utility fields:

@Service
public class UtilityService {

    // ❌ FATAL MISTAKE: client will remain null!
    @Autowired
    private static DatabaseClient client;
}

Why This Fails:

AutowiredAnnotationBeanPostProcessor is a Bean Post-Processor. It executes on heap instances during the bean lifecycle’s populateBean() phase after an individual instance has been instantiated. Static fields belong to the java.lang.Class object in the ClassLoader, not to an instance in the heap. Spring explicitly ignores static fields during injection.

Workaround (If Static Access is Unavoidable):

Use a non-static setter method:

@Service
public class UtilityService {

    private static DatabaseClient client;

    @Autowired
    public void setClient(DatabaseClient client) {
        UtilityService.client = client; // Assigns instance reference to static field
    }
}

Advanced Resolution via ObjectProvider<T>

ObjectProvider<T> (introduced in Spring 4.3 / 5.0) provides a modern, type-safe alternative to @Autowired(required = false):

@Service
public class MetricsReporter {

    private final ObjectProvider<PushGatewayClient> clientProvider;

    public MetricsReporter(ObjectProvider<PushGatewayClient> clientProvider) {
        this.clientProvider = clientProvider;
    }

    public void report() {
        // 1. Safe null-checked resolution (does not throw NoSuchBeanDefinitionException)
        PushGatewayClient client = clientProvider.getIfAvailable();
        if (client != null) {
            client.push();
        }

        // 2. Safe default fallback using Supplier lambda
        PushGatewayClient safeClient = clientProvider.getIfAvailable(NoOpClient::new);

        // 3. Process stream of all available beans in @Order sequence:
        clientProvider.orderedStream().forEach(PushGatewayClient::flush);
    }
}

4 Tricky Questions

1. What is the exact sequence of steps Spring executes to resolve a dependency when encountering @Autowired?

Answer: Spring follows a strict 5-tier resolution pipeline:

  1. byType: Queries the BeanFactory for all candidate beans assignable to the declared target type.
  2. @Qualifier Filter: If multiple candidates are found, filters beans matching the specified @Qualifier name.
  3. @Primary Check: If ambiguity remains, searches for a candidate annotated with @Primary.
  4. @Priority Check: If no @Primary exists, selects the candidate with the lowest integer value on jakarta.annotation.Priority.
  5. Fallback byName: Matches the field or parameter variable name directly against registered bean names. If more than one candidate survives or zero candidates exist without required = false, Spring throws NoUniqueBeanDefinitionException or NoSuchBeanDefinitionException.

2. Why does placing @Autowired on a static field fail silently and leave the field as null?

Answer: Spring manages instances of objects in the application context. Dependency injection is performed by AutowiredAnnotationBeanPostProcessor during the populateBean() phase of the bean lifecycle, which operates strictly on heap object instances.

static fields belong to the java.lang.Class object loaded in the JVM ClassLoader, not to any managed bean instance. Spring’s injection processor deliberately ignores static fields and methods to prevent race conditions and preserve container encapsulation.

3. What happens if you inject List<PaymentService> and three beans implement that interface? How is the order determined?

Answer: Spring detects that the target is a collection type, retrieves all three beans assignable to PaymentService, and injects them as an ArrayList<PaymentService>.

The order of elements inside the list is determined by:

  1. The @Order(int) annotation on the candidate classes or @Bean methods.
  2. The org.springframework.core.Ordered interface.
  3. The standard jakarta.annotation.Priority annotation.

Candidates with lower numerical values (e.g. @Order(1)) are positioned first in the list. Unordered beans are placed at the end in non-deterministic order.

4. What advantages does ObjectProvider<T> provide over @Autowired(required = false)?

Answer:

  1. Avoids NullPointerExceptions: @Autowired(required = false) assigns null to the field when no bean is found. ObjectProvider provides functional methods like getIfAvailable(Supplier) to supply defaults safely.
  2. Consumer Callbacks: Supports ifAvailable(Consumer<T>) to execute logic only if the bean is present.
  3. On-Demand Lazy Resolution: Defers bean instantiation until getObject() or getIfAvailable() is called.
  4. Streaming Collections: Provides orderedStream() to iterate over and filter matching beans in @Order sequence.

🎯 Interview Cheat Sheet

30-Second Elevator Pitch

@Autowired provides automated dependency injection (DI) in Spring, processed under the hood by AutowiredAnnotationBeanPostProcessor.

  • Resolution Algorithm: Resolves byType first, then narrows candidates via @Qualifier $\to$ @Primary $\to$ @Priority $\to$ byName fallback. Throws NoUniqueBeanDefinitionException on unresolved ambiguity.
  • Constructor Injection: The recommended industry standard; @Autowired is optional on single-constructor classes since Spring 4.3.
  • Collection Injection: Injects List<T> (sorted by @Order) or Map<String, T> (bean name $\to$ bean instance).
  • Modern Alternative: Use ObjectProvider<T> instead of @Autowired(required = false) for lazy, safe, and streamable injection.
  • Limitation: Does not work on static fields.

Candidate Resolution Priority

Step Rule Resolves When…
1 byType Exactly one bean matches target type
2 @Qualifier Bean matches specified qualifier name
3 @Primary One candidate has @Primary
4 @Priority Candidate has lowest numerical priority
5 byName Field/parameter name matches bean name

Red Flags (DO NOT Say)

  • ❌ “Spring resolves @Autowired dependencies by bean name first.” (It always resolves byType first; byName is only the last fallback).
  • ❌ “You must place @Autowired on every constructor in Spring Boot 3.” (It is optional on single-constructor classes).
  • ❌ “You can annotate static fields with @Autowired to initialize utility classes.” (Static fields are ignored by Spring’s bean post-processors and remain null).
  • ❌ “Field injection is faster than constructor injection.” (Field injection incurs heavy runtime reflection costs via setAccessible(true)).