Common Pitfalls of @Autowired in Spring: Circular Dependencies and Bean Name Conflicts
This article explains the core concepts of Spring's @Autowired dependency injection, illustrates common mistakes such as unresolved circular dependencies and bean name collisions, and provides detailed solutions including constructor injection, qualifier usage, and bean priority annotations.
Spring's Dependency Injection (DI) is a fundamental concept of the Spring framework that manages and decouples component dependencies. The @Autowired annotation simplifies injection but can cause problems when misused.
The annotation can be applied to fields, constructors, or methods, each representing a different injection scenario:
Field injection : @Autowired on a field lets Spring automatically find and inject a matching bean.
Constructor injection : @Autowired on a constructor is considered the best practice because it guarantees that all required dependencies are provided at object creation.
Method injection : @Autowired on a method lets Spring call the method to inject the required bean.
Improper use of @Autowired often leads to two major issues:
1. Unresolved Circular Dependencies
When two beans depend on each other via constructor injection, Spring cannot resolve the cycle because the bean instance is not fully created when the constructor is invoked. The following example demonstrates a circular dependency between UserManageService and UserService :
@Slf4j
@Service
public class UserManageService {
public UserService userService;
@Autowired
public UserManageService(UserService userService) {
this.userService = userService;
}
} @Service
@Slf4j
public class UserService {
private UserManageService userManageService;
@Autowired
public UserService(UserManageService userManageService) {
this.userManageService = userManageService;
}
}Running this code produces a bean creation error indicating a cycle:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| userManageService defined in file UserManageService.class]
↑ ↓
| userService defined in file UserService.class]
└─────┘Spring resolves most circular dependencies by exposing a partially constructed bean as a "early reference" using a three‑level cache (singletonObjects, earlySingletonObjects, singletonFactories). However, this mechanism works only for property (field) injection; constructor injection cannot be satisfied because the bean instance does not exist yet.
2. Bean Name Conflicts
If the container contains multiple candidates for a required type, @Autowired fails with the message "required a single bean, but 2 were found". This situation can be handled by:
Using @Qualifier to specify the exact bean name.
Marking a bean with @Primary or @Priority to give it higher precedence.
Ensuring only one bean of the required type is defined.
The actual injection work is performed by AutowiredAnnotationBeanPostProcessor . During the populateBean phase, it iterates over all BeanPostProcessor instances and calls postProcessProperties , which in turn finds injection metadata and invokes the inject method on each InjectedElement (field or method).
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
for (InjectedElement element : elementsToIterate) {
element.inject(bean, beanName, pvs);
}
}For field injection, the element resolves the dependency via beanFactory.resolveDependency , applies any @Qualifier or @Primary logic, and finally sets the field value via reflection.
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
Field field = (Field) this.member;
Object value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
if (value != null) {
ReflectionUtils.makeAccessible(field);
field.set(bean, value);
}
}Understanding these mechanisms helps avoid the two common pitfalls described above.
Conclusion
The @Autowired annotation is one of the most frequently used features in Spring development, but incorrect usage can lead to circular dependency errors and bean ambiguity. By preferring constructor injection, applying @Qualifier , @Primary , or @Priority where appropriate, and being aware of Spring's three‑level cache for early bean exposure, developers can write more robust and maintainable code.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.