Chapter 2.2☕ 16 min read

Dependency Injection (Autowired)

Constructor injection is the way. Field injection is the trap.

01The Concept: Dependency Injection

The Amazon Delivery vs Self-Pickup Analogy:

If you need a new phone:

Without DI (Tight Coupling): You go to the factory yourself, build the phone, and bring it home. You are entirely dependent on the factory. If the factory closes, your work stops.

With DI (Loose Coupling): You order on Amazon saying "I need a phone." You don't care which warehouse Amazon gets it from. Amazon delivers it to your door. Here, Amazon is the Injector.

In Spring, the @Autowired annotation is the Amazon delivery boy. You write @Autowired in your class, and Spring automatically "injects" (delivers) the object into your variable.

02Types of Dependency Injection
  1. Field Injection: @Autowired directly on the variable. Easy but not recommended — makes testing hard and fields cannot be final.
  2. Setter Injection: @Autowired on a setter method. Useful for optional dependencies.
  3. Constructor Injection: Passing dependency via constructor. Highly Recommended by Spring team. Enables immutable final fields and easy testing with mocks.

In Spring Boot 3, if a class has only one constructor, @Autowired is optional!

03Full Working Code: Constructor Injection
package com.devinhyderabad;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;

@SpringBootApplication
public class Application implements CommandLineRunner {

@Autowired
private UserService userService;

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}

@Override
public void run(String... args) throws Exception {
userService.saveUser("Ramesh");
}
}

@Repository
class DatabaseConnection {
public void save(String data) {
System.out.println("Saved " + data + " to H2 Database.");
}
}

@Service
class UserService {
private final DatabaseConnection dbConnection;

// Constructor Injection — @Autowired optional in Boot 3
public UserService(DatabaseConnection dbConnection) {
this.dbConnection = dbConnection;
}

public void saveUser(String name) {
dbConnection.save(name);
}
}
04Anti-Pattern: Field Injection

Field Injection — The Anti-Pattern

@Service
class BadUserService {
@Autowired
private DatabaseConnection dbConnection; // Not final, hard to test

public void saveUser(String name) {
dbConnection.save(name);
}
}

Why avoid it? You can't make the field final. Unit testing requires Spring context or Reflection. Constructor injection solves all these problems.

05Why It Matters / Interview Note

Interview Question: "Why is Constructor Injection preferred over Field Injection?"

Answer: (1) Immutability: Constructor injection allows final dependencies, ensuring they cannot change after initialization. (2) Testability: You can pass mock objects to the constructor without Spring's test context. (3) Fail-fast: Missing dependencies fail at startup, not as NullPointerException at runtime.

Enterprise Note: All modern enterprise codebases strictly ban Field Injection using tools like SonarQube. Always use Constructor Injection.

Key Takeaways

  • ✅ @Autowired tells Spring to inject a dependency into your class
  • ✅ Constructor Injection is recommended over Field Injection
  • ✅ Constructor Injection enables immutability (final fields) and testability
  • ✅ Single-constructor classes don't need @Autowired in Spring Boot 3