A Spring Boot field injection gotcha
A deep dive into circular dependencies, the way Spring Boot registers components, and how the name of a class can be the difference between functional and non-functional code
A couple of days ago I ran into a fairly standard problem with cyclic dependencies between two beans. A ContractService was responsible for sending a Contract to a 3rd party API called SomeAPI. This involved mapping it to the model expected by the API, which was done by SomeApiContractMapper, a different component that was injected into ContractService. Nothing fancy.
In this particular scenario, the mapping involved calculating a certain value from the contents of the Contract — a calculation which was exposed as a method on ContractService. This meant that ContractService needed to inject SomeApiContractMapper, and SomeApiContractMapper needed to inject ContractService.
@Service
public class ContractService {
private final SomeApiContractMapper someApiContractMapper;
public ContractService(
SomeApiContractMapper someApiContractMapper
) {
this.someApiContractMapper = someApiContractMapper;
}
// Stuff
}
// ...
@Component
public class SomeApiContractMapper {
private final ContractService contractService;
public SomeApiContractMapper(
ContractService contractService
) {
this.contractService = contractService;
}
// Stuff
}The actual code was written in Kotlin, however I’m translating to Java to target a wider audience. Constructor injection is the norm in Kotlin, and is preferred in Java as well.
As per the documentation of
de>AutowiredAnnotationBeanPostProcess , if a class only declares a single constructor, it will always be used, even if not annotated with@Autowired
In reality, the situation was a little more involved than what I’m describing here, so I didn’t realize I had introduced a dependency cycle until after I tried to build the app.

For reasons that are not relevant to this discussions, refactoring the application to remove the cycle (which is what you should always try to do) wasn’t something I wanted to get into. “Not to worry”, I thought to myself, “this is precisely what field injection was meant to solve” and changed the definition of SomeApiContractMapper accordingly:
@Component
public class SomeApiContractMapper {
@Autowired
private ContractService contractService;
// Stuff
}To my astonishment, this did not solve the error.

This really bugged me, because it made no sense. Obviously, Spring should be initializing SomeApiContractMapper first, since it doesn’t use constructor injection, and initialize ContractService next, which should work fine. So what was the problem?
On a hunch, I did something crazy — I changed the name of SomeApiContractMapper to AbcApiContractMapper. And lo and behold, the app started up.
I wasn’t happy. Not one bit.
It turns out that Spring isn’t as smart as one might like to think. One might assume that, once candidate components are loaded, some sort of analyzer is run to determine which components to initialize first, to avoid these types of problems. At the very least, one would hope that the list is deterministically sorted before initialization proceeds.
Unfortunately, this is not the case. As of Spring 5.3.22, components are loaded by instances of ClassPathBeanDefinitionScanner, specifically in the method scanCandidateComponents(String basePackage) of its parent, ClassPathScanningCandidateComponentProvider. There, a list of resources is loaded by delegating to an implementation of ResourcePatternResolver. All instances of ApplicationContext implement this interface, and by default when using Spring Boot, an instance of AnnotationConfigApplicationContext is used, which in turn delegates to an instance of PathMatchingResourcePatternResolver. Eventually, the method doFindAllClassPathResources(String path) is called, which uses a class loader to load the classes.
As it turns out, the output from this process is then used verbatim, and no further sorting or analysis is performed. In other words, the order in which beans are initialized is determined (in part) by the order in which the corresponding classes are loaded by the class loader. This order is almost certainly OS dependent, since it must, at some point, delegate to a “load files in directory X” system call, and file ordering is OS dependent. Unless you’re using a custom class loader and dealing with this explicitly, this means that, fundamentally, the order in which beans are initialized is non-deterministic.
This is the root cause of the observed behavior — the class loader implementation in my environment loads classes in alphabetical order. When the class having the field-level injection is named SomeApiContractMapper, it is loaded after ContractService. This means that ContractService gets initialized first, a candidate constructor for autowiring is detected, which in turn causes an attempt to initializeSomeApiContractMapper, which finds out it needs an instance of ContractService and fails. It does not matter that ContractService is injected via field injection — while field injection does happens after constructor injection (see doCreateBean, which first calls createBeanInstance, which is where constructor injection happens, and then deals with additional autowiring) it still happens in the same phase of the configuration.
However, when the class is named AbcApiContractMapper, no constructor autowiring is performed, and the bean gets loaded (and, therefore, initialized) before ContractService, and this problem never happens — once we get to initializing ContractService, AbcApiContractMapper is already initialized, cached and ready to be injected. We’re assuming the spring.main.allow-circular-references property is set to true — it is false by default starting in Spring Boot 2.6, which caused builds to suddenly break after upgrading (ours included).
All this is confirmed by turning on trace logging, and looking at the difference between the runs of ClassPathBeanDefinitionScanner for different choices of the mapper name.
Possible solutions
For pedagogical reasons, I will briefly mention that there are ways to circumvent this.
One possibility is using the @DependsOn annotation, which solves the problem:
@Component
@DependsOn("someApiContractMapper")
public class ContractService {
// ...
}This still requires the allow-circular-references property to be set to true, and is a terrible idea for many other reasons, such as hardwiring class names.
Another possibility is to mark the autowired field as @Lazy:
@Component
public class SomeApiContractMapper {
@Autowired
@Lazy
private ContractService contractService;
// ...
}This does not require the allow-circular-references property to be set, and, if there really isn’t any way you can refactor your code to remove dependency cycles, is probably what I would recommend.
However, whenever possible, refactor your code. If you’ve got a circular dependency, it almost always means you haven’t designed your code right — it’s a symptom of a deeper problem, a design problem, which will not get solved by throwing annotations at it and is almost certain to cause more and more problems down the road. Refactoring it will only get more costly as time progresses, and will never again be as easy as it is now.
In my specific instance, the correct solution was to realize that the logic that deals with propagating contracts to a third-party API deserved a special service of its own, because, from a business perspective, there was actually a lot more that fit nicely into this service — for example, marking a Contract as signed or checking it’s state in the remote systems, and other use-cases. Before we refactored, these implementations were sort of randomly strewn across several services. By removing this logic from ContractService and into SomeApiContractService, we were free to inject ContractService in the mapper, and also increased clarity of the codebase as a whole. The deeper problem was that we weren’t concentrating logic related to the same problem in the same place.
Have you heard about the Kotlin Primer? It’s an opinionated guide to the Kotlin language, and will transform anyone who knows Java into a Kotlin expert. Check it out!






