Skip to main content
Advertisement

Pro Tips — Bean Management and Defending Circular Dependencies

One of the most common issues you'll encounter in production with the Spring Framework is related to Bean Lifecycle Control and Circular Dependencies. This guide covers how to resolve these issues stably in a real-world environment.

1. What is a Circular Dependency?

A circular dependency occurs when Bean A depends on Bean B, and Bean B depends back on Bean A, causing the Spring IoC container to be unsure which Bean to create first. This typically results in a BeanCurrentlyInCreationException.

Bad Example (Circular Dependency)

@Service
public class OrderService {
private final PaymentService paymentService;

public OrderService(PaymentService paymentService) {
this.paymentService = paymentService;
}
}

@Service
public class PaymentService {
private final OrderService orderService;

public PaymentService(OrderService orderService) {
this.orderService = orderService;
}
}

[!WARNING] Since Spring Boot 2.6, circular dependencies are disallowed by default. The application will immediately throw an exception and shut down during startup.

2. Best Solution: Architectural Redesign

The most perfect solution is redesigning the architecture to break the circular loop. You can typically solve this by introducing an intermediary layer (like the Facade pattern) or using Application Events.

@Service
public class OrderFacade {
private final OrderService orderService;
private final PaymentService paymentService;

// Orchestrate both services from a higher layer without them knowing each other.
}

3. Practical Alternative: Lazy Initialization with @Lazy

If immediate structural refactoring is impossible (e.g., in legacy code), the most common production alternative is using @Lazy. Spring injects a proxy object at the time of bean creation and delays the actual initialization until the bean's method is invoked.

@Service
public class PaymentService {
private final OrderService orderService;

// Delays the injection of OrderService using @Lazy
public PaymentService(@Lazy OrderService orderService) {
this.orderService = orderService;
}

public void processPayment() {
// The real OrderService is initialized and invoked at this point.
orderService.updateOrderStatus();
}
}

[!TIP] @Lazy should be treated as a temporary bandage. Resolving the root cause through bounded contexts and decoupled objects is the best practice for lowering domain logic complexity.

Advertisement