Why Constructor Injection is Better Than Field Injection in Spring Boot?
Constructor Injection and Field Injection are two common ways to inject dependencies in Spring Boot. While both achieve dependency injection, Constructor Injection is widely recommended by the Springβ¦
Constructor Injection and Field Injection are two common ways to inject dependencies in Spring Boot. While both achieve dependency injection, Constructor Injection is widely recommended by the Spring team because it creates fully initialized objects, makes dependencies explicit, supports immutability, and detects configuration problems during application startup. In this article, we’ll explore why Constructor Injection is considered a best practice, compare it with Field Injection, and understand the differences through simple examples and real-world analogies.
NOTE: Spring verifies the required bean at application startup (runtime)
Constructor Injection in Spring Boot: Why Mandatory Dependencies Should Always Use Constructors
When you start learning Spring Boot, you’ll encounter multiple ways to inject dependencies into a class:
- Constructor Injection β (Recommended)
- Field Injection (
@Autowired) β (Not Recommended) - Setter Injection (For Optional Dependencies)
Many developers memorize that “Constructor Injection is the best practice.”
But the real question is:
Why?
The answer has very little to do with Spring and a lot to do with Object-Oriented Programming, Java, and building valid objects.
Let’s understand this from scratch.
What is Dependency Injection?
Imagine you’re creating a service responsible for managing products.
@Service
public class ProductService {
}
Can this service directly access the database?
No.
It needs another class that communicates with the database.
@Repository
public class ProductRepository {
}
Now, ProductService depends on ProductRepository.
ProductService
β
βΌ
ProductRepository
This relationship is called a dependency.
What is a Mandatory Dependency?
Some dependencies are absolutely required.
Without them, the class simply cannot perform its job.
For example:
ProductService
β
βΌ
ProductRepository
If there is no repository,
- products cannot be saved
- products cannot be updated
- products cannot be deleted
- products cannot be searched
So the repository is mandatory.
Constructor Injection
The recommended approach is:
@Service
public class ProductService {
private final ProductRepository repository;
public ProductService(ProductRepository repository) {
this.repository = repository;
}
}
Notice two important things.
1. The dependency is final
private final ProductRepository repository;
A final field can be assigned only once.
After the object is created, nobody can accidentally replace it.
This makes your class immutable with respect to its required dependency.
2. The constructor requires the dependency
public ProductService(ProductRepository repository) {
this.repository = repository;
}
The constructor is saying:
“I cannot create a ProductService unless someone gives me a ProductRepository.”
This is a contract.
Real-Life Example: Buying a Car
Imagine you’re buying a car.
A car consists of:
Car
βββ Engine (Mandatory)
βββ Steering (Mandatory)
βββ Wheels (Mandatory)
βββ Music System (Optional)
Can a car be manufactured without an engine?
No.
Can it run without steering?
No.
Can it exist without a music system?
Yes.
The engine and steering are mandatory dependencies.
The music system is an optional dependency.
Similarly,
ProductService
β
βΌ
ProductRepository
Without the repository, the service is incomplete.
How Spring Creates the Object?
Suppose your application starts.
Spring scans your project.
Spring Boot Application
β
βΌ
Find @Service
β
βΌ
Find ProductService
Spring notices that ProductService has a constructor.
public ProductService(ProductRepository repository)
Now Spring asks:
“To create ProductService, I first need ProductRepository.”
So the process becomes:
Spring Container
β
βΌ
Create ProductRepository
β
βΌ
Pass Repository to Constructor
β
βΌ
Create ProductService
β
βΌ
Application Ready
Every dependency is resolved before the object is created.
What Happens If the Repository Doesn’t Exist?
Suppose you accidentally delete:
@Repository
public class ProductRepository {
}
Now Spring tries to create ProductService.
Need ProductService
β
βΌ
Need ProductRepository
β
βΌ
Bean Found?
β
No
β
βΌ
Startup Failed
Spring immediately throws an error:
Parameter 0 of constructor in ProductService
required a bean of type
'ProductRepository'
that could not be found.
The application never starts.
At first, this might seem annoying.
In reality, it’s one of Spring’s biggest strengths.
Why Is Failing Fast a Good Thing?
Imagine an Inventory Management System.
ProductService
β
βββ ProductRepository
βββ CategoryRepository
βββ InventoryRepository
Suppose InventoryRepository is missing.
You have two possible situations.
Option A
The application starts.
Everything appears normal.
One hour later,
a customer clicks:
Add Product
Your code executes:
repository.save(product);
Boom.
NullPointerException
The customer sees an error.
Your production application crashes.
Option B
The application refuses to start.
Spring displays:
No qualifying bean of type
ProductRepository found.
The application never goes live.
You fix the issue immediately.
No customer is affected.
Failing at startup is much safer than failing in production.
Now Compare with Field Injection
Many beginners write:
@Service
public class ProductService {
@Autowired
private ProductRepository repository;
}
It looks shorter.
It looks cleaner.
But something very important is hidden.
Java Automatically Creates a Default Constructor
Since no constructor is written,
Java provides one automatically.
Conceptually:
public ProductService() {
}
Now Java allows this:
ProductService service = new ProductService();
The object has been created successfully.
But notice the problem.
repository == null
Spring has not injected anything yet.
What Happens Next?
Suppose someone writes:
ProductService service = new ProductService();
service.saveProduct(product);
Inside:
repository.save(product);
Since repository is null,
Java throws:
NullPointerException
The object existed,
but it was incomplete.
Why Doesn’t This Happen with Constructor Injection?
Look again.
public class ProductService {
private final ProductRepository repository;
public ProductService(ProductRepository repository) {
this.repository = repository;
}
}
Now try this:
new ProductService();
Java immediately stops you.
Constructor ProductService(ProductRepository)
cannot be applied to given types.
The compiler refuses to create an invalid object.
This protection comes from Java itself, not Spring.
Constructor Injection Enforces Valid Objects
Think about it.
With field injection:
Create Object
β
βΌ
Inject Dependency Later
There is a time when the object exists,
but its dependency is still null.
With constructor injection:
Need Dependency
β
βΌ
Create Object
The dependency comes first.
The object is born fully initialized.
Visual Comparison
Constructor Injection
Need ProductService
β
βΌ
Need ProductRepository
β
βΌ
Repository Available?
β
Yes ββββββββββΊ Create ProductService
β
No βββββββββββΊ Startup Failed
There is no possibility of an invalid object.
Field Injection
Create ProductService
β
βΌ
repository = null
β
βΌ
Inject Later
If something goes wrong before injection,
the object is already in an invalid state.
Another Real-Life Example
Imagine a restaurant.
The chef needs:
- Stove
- Gas
- Cooking utensils
Can the chef start cooking without a stove?
No.
The restaurant owner would never hire a chef and say:
“We’ll install the stove later.”
Instead,
the kitchen is prepared first,
then the chef starts working.
Constructor injection follows the same principle.
The service is created only after all required resources are available.
Advantages of Constructor Injection
1. Prevents NullPointerException
Dependencies are guaranteed to exist before the object is created.
2. Supports Immutable Objects
private final ProductRepository repository;
The dependency cannot be changed accidentally.
3. Makes Dependencies Explicit
Anyone reading the constructor immediately knows what the class needs.
public ProductService(ProductRepository repository)
The required dependency is obvious.
4. Enables Fail-Fast Startup
Configuration problems are detected when the application starts, not when users are interacting with it.
5. Easier Unit Testing
Testing becomes straightforward because dependencies can be supplied directly.
ProductRepository repository = Mockito.mock(ProductRepository.class);
ProductService service =
new ProductService(repository);
No Spring container is required.
6. Recommended by the Spring Team
Since Spring Framework 4.3, if a class has only one constructor, you don’t even need @Autowired.
@Service
public class ProductService {
private final ProductRepository repository;
public ProductService(ProductRepository repository) {
this.repository = repository;
}
}
Spring automatically uses that constructor.
This keeps the code cleaner and emphasizes constructor injection as the preferred approach.
Constructor vs Field Injection
| Constructor Injection | Field Injection |
|---|---|
| Dependency required before object creation | Dependency injected after object creation |
| Prevents invalid objects | Object can exist with null dependencies |
Supports final fields | Cannot use final fields |
| Easy to unit test | Difficult to test without reflection or Spring |
| Dependencies are explicit | Dependencies are hidden |
| Detects missing beans during startup | Can lead to runtime NullPointerException |
| Recommended by Spring | Generally discouraged for required dependencies |
Key Takeaways
- Use Constructor Injection for mandatory dependencies.
- A constructor defines the minimum requirements needed to create a valid object.
- Java prevents creating the object without those required dependencies.
- Spring resolves all constructor dependencies before instantiating the bean.
- If a dependency is missing, Spring fails the application startup immediately, helping you catch configuration issues early.
- Constructor injection works naturally with
finalfields, immutability, and unit testing. - Field injection may appear simpler, but it can create objects in an invalid state and makes testing and maintenance harder.
Rule of Thumb: If your class cannot function without a dependency, make it a constructor parameter. Let Java and Spring enforce that rule for you.