0

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?

New contributor
Andrea is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
9
  • 1
    Please include the full stacktrace / error. You cleaned it up removing valuable information needed for answering the question. Commented Nov 25 at 9:02
  • @M.Deinum Thank you, I have added the complete stack trace. Commented Nov 25 at 9:40
  • how do you expect it to find a Bean of ExampleDTO? That doesn't seem to be a Spring component/service/.... that is why 'new ExampleDTO(...)' does work Commented Nov 25 at 9:54
  • You can use getBean with manually provided dependencies for Spring to inject. Hence the getBean(Class, Object... args) method on the BeanFactory/ApplicationContext. Commented Nov 25 at 9:59
  • When I switch your sample to field injection it doesn't work either. I can only get it to work with constructor injection if you provide all dependencies (so including the service) or make the service field injection and the provided constructor only takes the ExampleDto. Commented Nov 25 at 10:13

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.