Spring BootSpring Corebeginner
Updated:

@Autowired in Spring: How Autowiring Really Works

6 min read

What @Autowired actually does under the hood, the three injection styles, how Spring resolves by type, and how to break ties with @Qualifier.

TL;DR – Quick Answer

@Autowired tells Spring to inject a matching bean from the container into a field, setter or constructor parameter. Spring resolves the dependency primarily by type, and if more than one candidate matches it narrows down by @Qualifier or @Primary. Constructor injection is the recommended style because it makes dependencies mandatory and testable.

On This Page

@Autowired is the annotation most beginners meet first and understand last. It looks like magic: you declare a field, add one line above it, and suddenly a fully built object appears. There is no magic — just a container matching your request against the beans it manages. This tutorial shows exactly what that matching does, the three places you can apply it, and how to fix the errors it produces.

What @Autowired asks Spring to do

At its core, @Autowired is a request: "find a bean in the container whose type is assignable to this target, and put it here." It is the visible face of dependency injection, which is itself the concrete mechanism behind inversion of control. You never call new on the dependency; Spring supplies an instance it already created and manages.

Because resolution is driven by type, the target's declared type is what matters. If you autowire a NotificationService and exactly one bean of that type exists, Spring injects it. The annotation says nothing about which concrete class — that is decided by what is registered in the container.

The three injection styles

You can apply @Autowired in three positions, and the choice has real consequences.

Field injection

@Service
public class OrderService {
    @Autowired
    private InventoryClient inventory; // concise, but hard to test
}

This is the shortest form and the one tutorials overuse. Its problem is that the field is private and non-final, so a unit test cannot supply a fake InventoryClient without reflection or a full Spring context.

Setter injection

@Service
public class OrderService {
    private InventoryClient inventory;

    @Autowired
    public void setInventory(InventoryClient inventory) {
        this.inventory = inventory;
    }
}

Setter injection suits genuinely optional dependencies and allows reconfiguration after construction, but it leaves the object in a half-built state between construction and the setter call.

Constructor injection

@Service
public class OrderService {
    private final InventoryClient inventory;

    // @Autowired is optional here — single constructor
    public OrderService(InventoryClient inventory) {
        this.inventory = inventory;
    }
}

This is the style to standardise on. The dependency is final, guaranteed to be present, visible in the signature, and trivially supplied in a test: new OrderService(fakeClient). Since Spring 4.3, a class with a single constructor does not even need the annotation.

Pro tip: If your constructor is growing to five or six parameters, that is not a reason to switch back to field injection — it is a design smell telling you the class has too many responsibilities. Split it before you hide the problem.

How Spring picks a bean when types collide

The interesting behaviour appears when more than one bean matches the requested type. Suppose you have two payment implementations:

public interface PaymentProcessor { void pay(double amount); }

@Component
public class UpiProcessor implements PaymentProcessor {
    public void pay(double amount) { System.out.println("UPI: " + amount); }
}

@Component
public class CardProcessor implements PaymentProcessor {
    public void pay(double amount) { System.out.println("Card: " + amount); }
}

Now this fails at startup:

@Service
public class Checkout {
    @Autowired
    private PaymentProcessor processor; // NoUniqueBeanDefinitionException
}

Spring finds two candidates for PaymentProcessor and refuses to guess. You resolve it in one of three ways: mark one bean @Primary, match by name, or state the exact bean with @Qualifier.

@Service
public class Checkout {
    private final PaymentProcessor processor;

    public Checkout(@Qualifier("upiProcessor") PaymentProcessor processor) {
        this.processor = processor;
    }
}

The qualifier value is the bean name, which defaults to the class name with a lowercase first letter — UpiProcessor becomes upiProcessor. This resolution flow, matching by type then narrowing by qualifier, is the single most common Spring interview follow-up on this topic.

Common mistake: Beginners assume @Qualifier matches by the interface or by a label they invented. It matches the bean name. If you rename the component or register it with a custom name, the qualifier string must change with it, or injection breaks.

Optional dependencies and collections

Sometimes a dependency genuinely may not exist. required = false leaves the field null when no bean is found, though modern code prefers Optional:

@Service
public class AuditService {
    private final Optional<MetricsPublisher> metrics;

    public AuditService(Optional<MetricsPublisher> metrics) {
        this.metrics = metrics; // empty if no MetricsPublisher bean exists
    }
}

You can also autowire all beans of a type into a List or Map. Injecting List<PaymentProcessor> gives you every implementation, which is a clean way to build plugin-style designs where each processor registers itself simply by being a component.

Circular dependencies and how they surface

A trap worth knowing before you hit it in production: if bean A needs bean B and bean B needs bean A, you have a circular dependency. With constructor injection this fails hard at startup, because neither object can be constructed before the other exists:

@Service
public class A {
    public A(B b) { } // needs B first
}

@Service
public class B {
    public B(A a) { } // needs A first — deadlock at startup
}

Spring throws a BeanCurrentlyInCreationException and refuses to start. That failure is a gift — it forces you to fix a genuine design problem. Field or setter injection can sometimes hide the cycle by injecting a half-built bean later, which lets the app start but leaves a fragile initialisation order.

The right fix is almost never a technical workaround like @Lazy; it is to break the cycle. Extract the shared logic into a third bean that both A and B depend on, so the dependency graph becomes a tree again. That constructor injection exposes the cycle at startup, while field injection can mask it, is one more argument for preferring constructor injection.

Interview note: If asked "how does constructor injection help with circular dependencies", the precise answer is that it does not resolve them — it detects them at startup instead of letting a broken object graph run silently. Framing it as detection rather than resolution shows real understanding.

Testing benefits you feel immediately

The strongest everyday argument for constructor injection is testing. Because dependencies arrive as parameters, you can build the object in a plain unit test with no Spring context at all:

class OrderServiceTest {
    @Test
    void appliesDiscount() {
        InventoryClient fake = new FakeInventoryClient();
        OrderService service = new OrderService(fake); // no Spring needed
        // assert on service behaviour with the fake
    }
}

Try that with field injection and you cannot set the private field without reflection or a running container, which makes fast unit tests painful. This single practical difference is why code reviewers flag field injection: it quietly makes the class harder to test, and untested services are where bugs hide.

Where @Autowired sits in the bean's life

Injection is not the first thing that happens to a bean. Spring calls the constructor, then performs autowiring, and only then runs initialisation callbacks — the full order is covered in the bean lifecycle tutorial. This is why an @Autowired field is still null inside the constructor with field injection, and why constructor injection sidesteps the problem entirely: the value arrives as a parameter, before the object even exists.

Every autowired object is a managed Spring bean pulled from the same container, so the same resolution rules apply whether you are wiring a repository into a service or a service into a controller across a whole Spring Boot application.

Interview relevance

Expect to be asked to compare the three injection styles and justify a preference — the strong answer is constructor injection for immutability and testability. Be ready to explain what happens when two beans match the same type, and to name the three resolution tools (@Primary, bean-name matching, @Qualifier). A common trap is asking whether @Autowired is mandatory on a single-argument constructor; the correct answer is no, not since Spring 4.3. Practise these alongside the broader Spring Boot annotations interview questions so you can move smoothly from @Autowired to @Component, @Service and @Configuration.

Frequently Asked Questions

Is @Autowired required on constructors in modern Spring?
No. If a class has a single constructor, Spring automatically uses it for injection since Spring 4.3, so the @Autowired annotation on that constructor is optional. You only need it explicitly when a class has more than one constructor and you want to mark which one Spring should use.
What is the difference between field and constructor injection?
Field injection places @Autowired directly on a private field, which is concise but hides dependencies and makes unit testing harder. Constructor injection passes dependencies as constructor parameters, which makes them mandatory, final and easy to supply in tests. Most teams standardise on constructor injection.
How does Spring resolve @Autowired when two beans match?
Spring first matches by type. If two beans share that type it looks for one marked @Primary, and failing that it matches the field or parameter name against a bean name. If ambiguity remains you must add @Qualifier with the exact bean name, otherwise startup fails with NoUniqueBeanDefinitionException.
What does @Autowired(required = false) do?
It tells Spring the dependency is optional. If no matching bean exists the field is simply left null instead of throwing an exception at startup. Modern code usually prefers wrapping the dependency in Optional or using @Nullable rather than relying on required = false.
Why is constructor injection preferred over field injection?
Constructor injection lets you mark dependencies final, guarantees they are set before the object is used, and exposes them clearly in the constructor signature. It also allows you to create the object in a test without a Spring context. Field injection offers none of these and is discouraged in code reviews.

Want to Build Your Career in Java Full Stack with AI?

Join CodeBegun and train with working industry engineers — Explore the Program

Apply for Demo Class →
Siva Prasad Galaba
Founder, CodeBegun · Staff Engineer

Founder of CodeBegun. 15+ years building Java systems at companies like Crunchyroll. Teaches Java, Spring Boot and system design the way the industry actually works, and mentors students through projects, mock interviews and placement preparation.

Technically reviewed by CodeBegun Technical TeamLast reviewed 15 July 2026 LinkedIn
Chat with us