Clean • Professional
A circular dependency occurs when two or more Spring beans depend on each other, either directly or indirectly, forming a loop.
Because of this loop, Spring cannot decide which bean to create first, leading to startup failure.
When Bean A depends on Bean B, and Bean B depends back on Bean A, a circular dependency is formed.
@Service
public class A {
@Autowired
private B b;
}
@Service
public class B {
@Autowired
private A a;
}
👉 What happens here?
A needs BB needs AA → B → C → A
Even indirect loops cause the same problem.
👉 Circular dependency is not just a Spring issue — it is a design smell.
Spring tries to resolve circular dependencies only in limited cases.
@Transactional, AOP edge cases)Spring uses three internal caches to break circular references:
| Cache | Purpose |
|---|---|
singletonObjects | Fully initialized beans |
earlySingletonObjects | Partially created beans |
singletonFactories | Bean proxy factories |
➡️ This mechanism works only for singleton beans
➡️ Constructor injection bypasses this mechanism intentionally
@Scope("prototype")
@Service
class A {
@Autowired B b;
}
@Scope("prototype")
@Service
class B {
@Autowired A a;
}
❌ Always fails
✔ Spring never resolves circular dependency for prototype beans
Problem Flow
Spring Start
↓
Create Bean A
↓
A needs B
↓
Create Bean B
↓
B needs A
↓
❌ Circular Dependency
↓
Startup Failure
Constructor injection fails fast, which is actually a good thing.
@Service
public class OrderService {
private final PaymentService paymentService;
public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}
Why this is best?
⚠️ Constructor injection does NOT allow circular dependency — and that’s intentional.
Move shared logic to a separate service.
@Service
public class CommonService {
public void commonLogic() {}
}
@Service
public class A {
private final CommonService commonService;
public A(CommonService commonService) {
this.commonService = commonService;
}
}
@Service
public class B {
private final CommonService commonService;
public B(CommonService commonService) {
this.commonService = commonService;
}
}
@Lazy Annotation (Workaround )@Service
public class A {
@Autowired
@Lazy
private B b;
}
@Service
public class B {
@Autowired
private A a;
}
Use only when refactoring is not possible
@Service
public class A {
private B b;
@Autowired
public void setB(B b) {
this.b = b;
}
}
@Service
public class B {
private A a;
@Autowired
public void setA(A a) {
this.a = a;
}
}
✔ Works in some cases
❌ Not ideal for mandatory dependencies
public interface NotificationService {
void notifyUser();
}
@Service
public class EmailService implements NotificationService {
public void notifyUser() {}
}
@Service
public class UserService {
private final NotificationService notificationService;
public UserService(NotificationService notificationService) {
this.notificationService = notificationService;
}
}
@EventListener)Instead of calling services directly, use events.
@Component
public class OrderService {
@Autowired
private ApplicationEventPublisher publisher;
public void placeOrder() {
publisher.publishEvent(new OrderCreatedEvent(this));
}
}
@Component
public class NotificationListener {
@EventListener
public void handleOrder(OrderCreatedEvent event) {
System.out.println("Order created notification sent");
}
}
Spring Boot 2.6+ disables this by default.
spring.main.allow-circular-references=true
When using:
@Transactional@Async@CacheableEarly bean references may bypass proxies, causing runtime issues.
👉 Another reason constructor injection is safest.
Circular dependency:
Constructor injection improves testability:
new OrderService(mockPaymentService);
@RequiredArgsConstructor
@Service
public class OrderService {
private final PaymentService paymentService;
}
| Solution | Recommendation Level | Notes |
|---|---|---|
| Refactor with third service | Strongly Recommended | Clean, scalable, and best long-term solution |
| Constructor injection | Highly Recommended | Enforces clean design and fails fast |
Events (@EventListener) | Highly Recommended | Loosely coupled and ideal for large systems |
| Interface-based design | Recommended | Improves flexibility and reduces coupling |
@Lazy | Conditionally Acceptable | Temporary workaround; may hide design issues |
| Setter injection | Limited Use | Works in specific cases; not ideal for mandatory dependencies |
| Allow circular references | Not Recommended | Hides architectural problems; avoid in production |
allow-circular-references in real projects