Circular dependencies are a common challenge in Spring Boot applications, often arising due to the intricate web of dependencies in complex systems. This article explores simple yet effective solutions to tackle circular dependencies and streamline dependency management

In Spring Boot, @Autowired can encounter issues when circular dependencies arise.

A circular dependency happens when two or more beans depend on each other, creating a loop that Spring cannot resolve during the application context creation.

For example, if BeanA depends on BeanB, and BeanB depends on BeanA, this results in a circular reference.

To address this, Spring provides the @Lazy annotation. Using @Lazy can help resolve circular dependencies by delaying the initialization of one of the beans until it’s needed. Additionally, you can refactor your design to avoid circular dependencies altogether, which is often a better approach for cleaner architecture. her in a loop, preventing successful bean initialization.

Real-world use cases:A classic example is managing dependencies between authentication filters and user services in a Spring Security configuration.

Lets look at the error and recommendation from the compiler:

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

???????
|  jwtAuthFilter defined in file [
    com\spsoft\springboot3security\filter\JwtAuthFilter.class]
?     ?
|  userInfoService (field private org.springframework.security.crypto.password.PasswordEncoder com.spsoft.springboot3security.service.UserInfoService.encoder)
?     ?
|  securityConfig defined in file [com\spsoft\springboot3security\config\SecurityConfig.class]
???????


Action:

Despite circular references being allowed, the dependency cycle between beans could not be broken. Update your application to remove the dependency cycle.


Here is the fix below using @Lazy:


@Service
public class UserInfoService implements UserDetailsService {

    @Autowired
    private UserInfoRepository repository;

    @Lazy
    @Autowired
    private PasswordEncoder encoder;

    // Other methods and logic
}

To resolve Circular Dependency, here are some steps to follow:

1. Refactor Dependencies

  • Avoid direct circular references: Redesign your beans or service dependencies so that one does not directly depend on the other. Sometimes introducing a mediator or separating concerns can resolve these issues.

2. Use @Lazy Annotation

  • Annotate one of the beans with @Lazy to delay its initialization until it is actually needed, breaking the initialization cycle.

Example:

   @Lazy
   @Autowired
   private UserInfoService userInfoService;

3. Use @Primary or @Qualifier

  • Ensure that Spring can resolve dependencies correctly by marking the bean with @Primary or explicitly defining which bean to inject using @Qualifier.

4. Check Configuration Classes

  • Sometimes circular dependencies can arise from configuration beans (@Configuration). Ensure that your SecurityConfig and any related components are properly structured and don’t inadvertently reference each other.

5. Constructor Injection

  • Prefer constructor injection over field injection. Constructor injection allows you to identify dependencies upfront and often helps in managing complex dependencies.

Example:

   @Autowired
   public JwtAuthFilter(UserInfoService userInfoService) {
       this.userInfoService = userInfoService;
   }

Here’s how we could refactor UserInfoService using Contructor Injection:


@Service
public class UserInfoService implements UserDetailsService {

    private final UserInfoRepository repository;
    private final PasswordEncoder encoder;

    @Autowired
    public UserInfoService(UserInfoRepository repository, PasswordEncoder encoder) {
        this.repository = repository;
        this.encoder = encoder;
    }

    // Your methods here
}

Why This is a Better Approach:

  1. Immutability: Dependencies are final and cannot be accidentally changed.
  2. Better Testability: Makes unit testing easier by allowing us to pass mock dependencies directly into the constructor.
  3. Clarity: It’s explicit which dependencies are required for the service to function, improving code readability.

In conclusion, addressing circular dependencies in Spring Boot is crucial for building efficient, maintainable applications. By employing techniques like @Lazy, constructor injection, and thoughtful refactoring, developers can ensure a cleaner architecture and smoother dependency management.

Author: Mohammad J Iqbal

Mohammad J Iqbal

Follow Mohammad J Iqbal on LinkedIn