Course topics

By WebNest Studio Engineering Team

Spring Framework Tutorial

IoC and Dependency Injection in Spring

Inversion of Control (IoC) is the central design principle behind the Spring Framework. In a traditional program, an object creates and manages the objects it depends on — a service class might call "new OrderRepository()" directly inside its own constructor. IoC flips this: the object no longer creates its dependencies; instead, some external container creates them and hands them to the object. Spring's IoC container is that external mechanism, and Dependency Injection (DI) is the specific technique it uses to supply those dependencies.

The benefit is not just less "new" typing — it is decoupling. When a class receives its collaborators from outside rather than constructing them itself, it becomes trivial to swap implementations (a real payment gateway for a test double), configure behavior differently per environment, and unit test classes in isolation. Spring's container reads configuration (annotations or XML), builds a graph of objects called beans, wires their dependencies together, and manages their entire lifecycle.

The IoC Container

Spring's IoC container is responsible for instantiating, configuring, and assembling beans. It reads metadata — typically Java annotations like @Component and @Autowired, or older XML bean definitions — to know what objects to create and how they depend on one another. The two main container interfaces are BeanFactory (the basic container) and ApplicationContext (the more feature-rich container almost every real application uses). Once the container starts, it builds the entire dependency graph up front for singleton beans, resolving each dependency before the object is considered ready to use.

Constructor Injection

Constructor injection supplies dependencies as arguments to the class's constructor. Spring's team and documentation recommend this as the default choice because it lets fields be declared final, guarantees the object is never in a partially-constructed, dependency-less state, and makes required dependencies explicit and impossible to forget. With a single constructor, @Autowired is not even required — Spring infers it automatically.

Setter Injection

Setter injection supplies dependencies through public setter methods after the object has been constructed with a no-argument (or partial) constructor. This is useful for optional dependencies that have a sensible default, or when a dependency needs to be reconfigured after construction. The tradeoff is that the object can briefly exist in an incomplete state before all setters are called.

Field Injection

Field injection applies @Autowired directly to a class field, letting Spring set it via reflection. It looks the shortest to write, but it is now widely discouraged: fields cannot be made final, the class cannot be instantiated normally outside a Spring container (making plain unit tests harder), and missing dependencies only surface at runtime rather than at compile or object-construction time.

  • Constructor injection — required dependencies, immutable fields, best testability.
  • Setter injection — optional dependencies, reconfigurable after construction.
  • Field injection — most concise, but hurts testability and hides missing dependencies until runtime.

Examples

Constructor injection wiring a service to a repository

Java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

@Repository
class OrderRepository {
    String findLatestOrderId() {
        return "ORD-1042";
    }
}

@Service
class OrderService {

    private final OrderRepository orderRepository;

    // A single constructor means @Autowired is optional here,
    // but Spring still performs constructor injection automatically.
    @Autowired
    public OrderService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    public void printLatestOrder() {
        System.out.println("Latest order: " + orderRepository.findLatestOrderId());
    }
}
Output
Spring creates the OrderRepository bean first, injects it into OrderService's constructor,
and calling printLatestOrder() prints: "Latest order: ORD-1042"

Setter injection for an optional dependency

Java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
class NotificationService {

    private EmailSender emailSender;

    @Autowired(required = false)
    public void setEmailSender(EmailSender emailSender) {
        this.emailSender = emailSender;
    }

    public void notifyUser(String message) {
        if (emailSender != null) {
            emailSender.send(message);
        } else {
            System.out.println("No email sender configured, skipping: " + message);
        }
    }
}
Output
If no EmailSender bean exists in the context, notifyUser("Order shipped") prints:
"No email sender configured, skipping: Order shipped"

Common Mistakes

  • Overusing field injection with @Autowired directly on fields, which makes classes impossible to construct with plain "new" in unit tests without reflection.
  • Creating circular dependencies (Bean A needs Bean B, Bean B needs Bean A) via constructor injection, which fails at startup rather than lazily resolving.
  • Forgetting that constructor injection requires Spring to manage the object — manually instantiating a class with "new" bypasses the container and injects nothing.
  • Mixing multiple constructors without marking the intended one with @Autowired, leaving Spring unable to decide which constructor to use.

Exercise

Using the lesson's Constructor injection wiring a service to a repository example, create a small implementation or design sketch that demonstrates IoC means the container creates and wires objects, instead of objects creating their own dependencies.. Add a test case or scenario that would catch Overusing field injection with @Autowired directly on fields, which makes classes impossible to construct with plain "new" in unit tests without reflection., and describe the expected result.

Show hints
  • Start with The IoC Container and compare your approach with Constructor injection wiring a service to a repository. Identify the decision or behavior the lesson recommends.
  • Walk through your scenario step by step. Confirm that it follows IoC means the container creates and wires objects, instead of objects creating their own dependencies. and that it would catch the failure described by Overusing field injection with @Autowired directly on fields, which makes classes impossible to construct with plain "new" in unit tests without reflection..

Quick quiz

1. Which choice is a mistake to avoid in IoC and Dependency Injection in Spring?

  • A. IoC means the container creates and wires objects, instead of objects creating their own dependencies.
  • B. Overusing field injection with @Autowired directly on fields, which makes classes impossible to construct with plain "new" in unit tests without reflection.
  • C. Creating circular dependencies (Bean A needs Bean B, Bean B needs Bean A) via constructor injection, which fails at startup rather than lazily resolving.
Show answer

B. Avoid this approach: Overusing field injection with @Autowired directly on fields, which makes classes impossible to construct with plain "new" in unit tests without reflection. The lesson recommends: IoC means the container creates and wires objects, instead of objects creating their own dependencies.

Interview questions

How would you apply the guidance in The IoC Container to a real task, and what failure would you guard against?

Show answer

IoC means the container creates and wires objects, instead of objects creating their own dependencies. Apply this in The IoC Container; the example Constructor injection wiring a service to a repository shows one way to do it. Avoid Overusing field injection with @Autowired directly on fields, which makes classes impossible to construct with plain "new" in unit tests without reflection. and explain how your implementation or design prevents that failure.

Key Points to Remember

  • IoC means the container creates and wires objects, instead of objects creating their own dependencies.
  • Constructor injection is the recommended default: immutable, explicit, and fully testable without a container.
  • Setter injection suits optional dependencies; field injection is easiest to write but weakest for testing.
  • The IoC container resolves the full dependency graph based on annotations or XML metadata before beans are used.

Practice in the playground

Write and run your own code for this topic, or start from the exercise or an example. Runs plain Java 17. Frameworks such as Spring, and databases, are not available here. Your code for each topic is kept in this browser.

Run the examples

Change an input, predict the result, then compare it with the output. Explain why the result changes.

These examples need a JDK project with their libraries on your computer, so they can be copied but not run here. Codelab runs Python, FastAPI backends with SQLite, plain Java 17 and HTML/CSS/JavaScript.

WebNest Studio

Written by

WebNest Studio Engineering Team

Practical lessons from developers working with Java, Python, React, databases and production systems.

Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering