I'm migrating from field injection (@Autowired) to constructor-based injection in a Spring Boot application (Java 21), but I'm running into a BeanCreationException when Spring tries to instantiate my command object.
The only dependency that Spring should autowire is the Service.
The DTO is not a Spring bean and must be provided manually by the controller, which is why I retrieve the command with:
beanFactory.getBean(Command.class, inputDto);
With field injection everything works, because Spring injects only the Service through the @Autowired annotation and I set the DTO afterward.
When I switch to constructor injection (marking dependencies as final), Spring can no longer find a matching constructor, since it tries to autowire both parameters (including the DTO, which is not a bean) and this results in a BeanCreationException.
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'exampleCommand' defined in file [...\example-project\target\diraliases\OPENSHIFT\classes\com\examplepackage\command\ExampleCommand.class]: Could not resolve matching constructor on bean class [com.examplepackage.command.ExampleCommand] (hint: specify index/type/name arguments for simple parameters to avoid type ambiguities. You should also check the consistency of arguments when mixing indexed and named arguments, especially in case of bean definition inheritance)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:291)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1395)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1232)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:357)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:227)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1613)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1571)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:564)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:393)
at com.examplepackage.controller.ExampleController.exampleApi(ExampleController.java:26)
[...]
It seems like Spring cannot decide which constructor to use, or is unable to resolve the parameters (the DTO parameter is dynamic, the service is a Spring bean).
Below are the code snippets:
Controller:
@RequiredArgsConstructor
@RestController
@RequestMapping(value = "/api", produces = {MediaType.APPLICATION_JSON_VALUE, "application/hal+json"})
public class ExampleController {
private final BeanFactory beanFactory;
@PostMapping(path = "/exampleApi")
public ResponseEntity<String> exampleApi(@RequestBody String stringDTO) throws Exception {
ExampleCommand command = beanFactory.getBean(ExampleCommand.class, new ExampleDto(stringDTO));
String resource = command.execute();
return ok(resource);
}
}
Command:
@Component
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class ExampleCommand {
private final ExampleDto exampleDto;
private final ExampleService exampleService;
public ExampleCommand(ExampleDto exampleDto, ExampleService exampleService) {
this.exampleDto = exampleDto;
this.exampleService = exampleService;
}
public String execute() {return exampleService.exampleMethod(exampleDto);}
}
Service:
@Service
public class ExampleService {
public String exampleMethod(ExampleDto exampleDto) {
return String.join(exampleDto.value(), "-joined");
}
}
DTO:
public record ExampleDto(String value) {}
What I expect
Spring should use the constructor that takes the service bean + the DTO passed in getBean(...), without requiring @Autowired on ExampleService.
What actually happens
Spring fails to resolve the constructor and throws the exception above.
Question
What am I doing wrong? How can I properly structure my constructor so that Spring can inject the service bean and accept the DTO (which is not a Bean) as a runtime parameter when using beanFactory.getBean(Command.class, dto)?
Do I need @Autowired on a specific constructor, or should I use another pattern?
getBeanwith manually provided dependencies for Spring to inject. Hence thegetBean(Class, Object... args)method on theBeanFactory/ApplicationContext.ExampleDto.