Circular Dependencies in Spring
This post is all about how to resolve circular dependency issue in Spring causing UnsatisfiedDependencyException
. In simple words when two services depend on each other causes the circular dependency issue.
Circular dependencies are the issue caused during dependency injection when spring-context tries to load objects and one bean depends on another bean. Suppose when Object A & B depends on each other i.e. A depends on B and vice-versa. Spring throws UnsatisfiedDependencyException
while creating objects of A and B because A object cannot be created until unless B is created and visa-versa.
Let’s understand it using the real code example. Create two services ServiceA
and ServiceB
and try to inject ServiceA
into ServiceB
and visa-versa as shown in the above picture.
package org.websparrow.service;
import org.springframework.stereotype.Service;
@Service
public class ServiceA {
private ServiceB serviceB;
public ServiceA(ServiceB serviceB) {
System.out.println("Calling Service A");
this.serviceB = serviceB;
}
}
package org.websparrow.service;
import org.springframework.stereotype.Service;
@Service
public class ServiceB {
private ServiceA serviceA;
public ServiceB(ServiceA serviceA) {
System.out.println("Calling Service B");
this.serviceA = serviceA;
}
}
To simulate the circular dependency issue, run the below class, and see the console log.
package org.websparrow;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class CircularDependenciesTestApp {
public static void main(String[] args) {
SpringApplication.run(CircularDependenciesTestApp.class, args);
}
}
When we execute CircularDependenciesTestApp
class it won’t be able to inject the dependencies due to circular dependencies on each other and will throw a checked exception as shown below:
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2020-05-27 21:22:46.368 ERROR 4480 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| serviceA defined in file [F:\sts4-workspace\circular-dependencies-spring\target\classes\org\websparrow\service\ServiceA.class]
↑ ↓
| serviceB defined in file [F:\sts4-workspace\circular-dependencies-spring\target\classes\org\websparrow\service\ServiceB.class]
└─────┘
How to resolve this issue?
To solve the circular dependency issue, you have two option:
1. Using @Lazy with constructor injection
We can lazily initialize ServiceB
bean during constructor injection in order to delay constructing ServiceB
bean. Here are the code changes in ServiceA
for more clarity:
package org.websparrow.service;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@Service
public class ServiceA {
private ServiceB serviceB;
public ServiceA(@Lazy ServiceB serviceB) {
System.out.println("Calling Service A");
this.serviceB = serviceB;
}
}
If you run the CircularDependenciesTestApp
class again, you’ll find the circular dependency issue is solved.
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.0.RELEASE)
2020-05-27 21:33:22.637 INFO 7156 --- [ main] o.w.CircularDependenciesTestApp : Starting CircularDependenciesTestApp on Atul-PC with PID 7156 (F:\sts4-workspace\circular-dependencies-spring\target\classes started by Atul in F:\sts4-workspace\circular-dependencies-spring)
2020-05-27 21:33:22.640 INFO 7156 --- [ main] o.w.CircularDependenciesTestApp : No active profile set, falling back to default profiles: default
Calling Service A
Calling Service B
2020-05-27 21:33:23.251 INFO 7156 --- [ main] o.w.CircularDependenciesTestApp : Started CircularDependenciesTestApp in 0.98 seconds (JVM running for 1.667)
2. Using @Autowired along with @Lazy annotation
Using @Autowired
along with @Lazy
annotation for injecting ServiceB
in ServiceA
. Let’s use these annotations to inject beans and test our application whether it resolves the issue:
package org.websparrow.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@Service
public class ServiceA {
@Autowired
@Lazy
private ServiceB serviceB;
/*
public ServiceA(ServiceB serviceB) {
System.out.println("Calling Service A");
this.serviceB = serviceB;
}
*/
}
Here is the output on the console log when you run CircularDependenciesTestApp
class again:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.0.RELEASE)
2020-05-27 21:45:07.583 INFO 4036 --- [ main] o.w.CircularDependenciesTestApp : Starting CircularDependenciesTestApp on Atul-PC with PID 4036 (F:\sts4-workspace\circular-dependencies-spring\target\classes started by Atul in F:\sts4-workspace\circular-dependencies-spring)
2020-05-27 21:45:07.586 INFO 4036 --- [ main] o.w.CircularDependenciesTestApp : No active profile set, falling back to default profiles: default
Calling Service B
2020-05-27 21:45:08.141 INFO 4036 --- [ main] o.w.CircularDependenciesTestApp : Started CircularDependenciesTestApp in 0.928 seconds (JVM running for 1.614)
Conclusion
In this tutorial, we have learned what is a circular dependency, when it occurs in the application and how to resolve it.
References
- Dependency injection
- Spring Constructor-based Dependency Injection Example
- Spring Properties Dependency Injection Example
- Spring Setter-based Dependency Injection Example