LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 918 min read

Constructor vs Setter Injection

Compare constructor and setter injection in Spring, their syntax and tradeoffs, and why constructor injection is the recommended default.

Introduction

Spring supports several ways to inject dependencies, but the two you will use in almost every project are constructor injection and setter injection. This lesson compares them directly — syntax, behavior, and tradeoffs — so you can make an informed choice instead of copying whatever style a tutorial happened to use.

Constructor Injection Syntax

With constructor injection, dependencies are declared as constructor parameters. Spring calls the constructor, supplying a matching bean for each parameter.

PaymentService.java (constructor injection)
package com.programinds.demo;
import org.springframework.stereotype.Service;
@Service
public class PaymentService {
private final PaymentGateway paymentGateway;
public PaymentService(PaymentGateway paymentGateway) {
this.paymentGateway = paymentGateway;
}
public void charge(double amount) {
paymentGateway.process(amount);
}
}
Behavior

Click Run to see what this code prints.

Setter Injection Syntax

With setter injection, Spring first creates the bean using a no-argument (or partially filled) constructor, then calls annotated setter methods to supply dependencies afterward.

PaymentService.java (setter injection)
package com.programinds.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class PaymentService {
private PaymentGateway paymentGateway;
@Autowired
public void setPaymentGateway(PaymentGateway paymentGateway) {
this.paymentGateway = paymentGateway;
}
public void charge(double amount) {
paymentGateway.process(amount);
}
}
Behavior

Click Run to see what this code prints.

Side-by-Side Comparison

AspectConstructor InjectionSetter Injection
ImmutabilityFields can be declared "final"Fields cannot be "final"
Guaranteed initializationObject is never in an incomplete stateObject can briefly exist without dependencies set
Required vs optional dependenciesBest for required dependenciesBetter suited for optional dependencies
Circular dependency detectionFails fast at startupCan sometimes work around circular dependencies
Testability without SpringVery easy — just call the constructorRequires calling setters manually

Why Constructor Injection Is Preferred

The Spring Team's Own Recommendation

The official Spring documentation recommends constructor injection for mandatory dependencies because it produces immutable, fully initialized objects and makes dependencies explicit in the class's public API — you can see everything a class needs just by looking at its constructor signature.

Constructor injection also plays well with plain unit testing. You can instantiate PaymentService directly with a mock PaymentGateway in a test, with no Spring container involved at all — the constructor is just regular Java.

Testing constructor-injected code (no Spring needed)
public class PaymentServiceTest {
public static void main(String[] args) {
PaymentGateway fakeGateway = amount ->
System.out.println("Fake charge: " + amount);
PaymentService service = new PaymentService(fakeGateway);
service.charge(49.99);
}
}
Console Output

Click Run to see what this code prints.

When Setter Injection Still Makes Sense

Setter injection is not obsolete — it is genuinely useful for optional dependencies, where a sensible default exists and the dependency may or may not be provided. It is also occasionally used to break a genuine circular dependency between two beans, though redesigning the classes to avoid the cycle is usually a better fix.

Combining Both

Spring allows constructor injection for required dependencies and setter injection for optional ones within the same class, giving you the immutability benefits where they matter most while still allowing flexibility for optional collaborators.

Mixing both styles
@Service
public class PaymentService {
private final PaymentGateway paymentGateway; // required
private FraudDetector fraudDetector; // optional
public PaymentService(PaymentGateway paymentGateway) {
this.paymentGateway = paymentGateway;
}
@Autowired(required = false)
public void setFraudDetector(FraudDetector fraudDetector) {
this.fraudDetector = fraudDetector;
}
public void charge(double amount) {
if (fraudDetector != null) {
fraudDetector.check(amount);
}
paymentGateway.process(amount);
}
}

Common Mistakes

Avoid These Mistakes
  • Using setter injection for required dependencies, which allows the object to exist in a half-initialized, invalid state.
  • Mixing many optional setter dependencies into a class that would be simpler with fewer, required constructor dependencies.
  • Forgetting that a single-constructor class does not need @Autowired at all in modern Spring — it is inferred automatically.

Best Practices

  • Default to constructor injection for anything the class cannot function without.
  • Reserve setter injection for genuinely optional collaborators with sensible defaults.
  • Avoid field injection (@Autowired directly on a field) in production code entirely — prefer constructor or setter injection instead.

Frequently Asked Questions

No, you can combine constructor injection for required dependencies with setter injection for optional ones in the same class.

Only if the class has more than one constructor; with a single constructor, Spring infers it automatically since Spring 4.3.

Spring Boot's own starter classes overwhelmingly use constructor injection, reflecting the framework team's own recommendation.

Key Takeaways

  • Constructor injection supplies dependencies at object creation time and supports immutable, "final" fields.
  • Setter injection supplies dependencies after construction via setter methods, suited to optional dependencies.
  • The Spring team recommends constructor injection for required dependencies.
  • Both styles can be combined within a single class when appropriate.

Summary

Constructor injection wins for required dependencies because it guarantees a fully initialized, immutable object and keeps dependencies visible in the class's API; setter injection remains a good fit for optional collaborators. Next, we will step back and look closely at what a "bean" actually is and how beans get registered in and retrieved from the container.

Next Lesson →

Spring Beans