Assuming the following test:

@SpringBootTest
@EnableAutoConfiguration
public class TestAutowireCandidate {

    @Test
    void test() {
    }

    @SpringBootConfiguration
    static class TestConfig {

        @Bean(autowireCandidate = false)
        Executor executor() {
            return new ThreadPoolTaskExecutorBuilder().corePoolSize(1).maxPoolSize(2).build();
        }

    }

}

And having spring-boot-starter-actuator and micrometer-core in dependencies the test fails with:

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: 
Error creating bean with name 'org.springframework.boot.actuate.autoconfigure.metrics.task.TaskExecutorMetricsAutoConfiguration': 
Unsatisfied dependency expressed through method 'bindTaskExecutorsToRegistry' parameter 0: 
No qualifying bean of type 'java.util.Map<java.lang.String, java.util.concurrent.Executor>' available: 
expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}

Reproducer: https://github.com/alexey-anufriev/autowire-candidate-error

Comment From: wilkinsona

Thanks for the report.

This appears to be a more general problem and is not specific to TaskExecutorMetricsAutoConfiguration. For example, if you define a DataSource with autowireCandidate = false, JdbcTemplateAutoConfiguration will fail like this:

 org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'jdbcTemplate' defined in org.springframework.boot.autoconfigure.jdbc.JdbcTemplateConfiguration: Unsatisfied dependency expressed through method 'jdbcTemplate' parameter 0: No qualifying bean of type 'javax.sql.DataSource' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:804)
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:546)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1351)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1181)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:563)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:336)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:296)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:334)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.instantiateSingleton(DefaultListableBeanFactory.java:1115)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingleton(DefaultListableBeanFactory.java:1086)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1025)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:987)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627)
    at org.springframework.boot.test.context.runner.AbstractApplicationContextRunner.configureContext(AbstractApplicationContextRunner.java:428)
    at org.springframework.boot.test.context.runner.AbstractApplicationContextRunner.createAndLoadContext(AbstractApplicationContextRunner.java:403)
    at org.springframework.boot.test.context.runner.AbstractApplicationContextRunner.lambda$4(AbstractApplicationContextRunner.java:389)
    at org.springframework.boot.test.context.assertj.AssertProviderApplicationContextInvocationHandler.getContextOrStartupFailure(AssertProviderApplicationContextInvocationHandler.java:61)
    at org.springframework.boot.test.context.assertj.AssertProviderApplicationContextInvocationHandler.<init>(AssertProviderApplicationContextInvocationHandler.java:48)
    at org.springframework.boot.test.context.assertj.ApplicationContextAssertProvider.get(ApplicationContextAssertProvider.java:113)
    at org.springframework.boot.test.context.runner.AbstractApplicationContextRunner.createAssertableContext(AbstractApplicationContextRunner.java:389)
    at org.springframework.boot.test.context.runner.AbstractApplicationContextRunner.consumeAssertableContext(AbstractApplicationContextRunner.java:362)
    at org.springframework.boot.test.context.runner.AbstractApplicationContextRunner.lambda$1(AbstractApplicationContextRunner.java:341)
    at org.springframework.boot.test.util.TestPropertyValues.lambda$5(TestPropertyValues.java:174)
    at org.springframework.boot.test.util.TestPropertyValues.applyToSystemProperties(TestPropertyValues.java:188)
    at org.springframework.boot.test.util.TestPropertyValues.applyToSystemProperties(TestPropertyValues.java:173)
    at org.springframework.boot.test.context.runner.AbstractApplicationContextRunner.lambda$0(AbstractApplicationContextRunner.java:341)
    at org.springframework.boot.test.context.runner.AbstractApplicationContextRunner.withContextClassLoader(AbstractApplicationContextRunner.java:369)
    at org.springframework.boot.test.context.runner.AbstractApplicationContextRunner.run(AbstractApplicationContextRunner.java:340)
    at org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfigurationTests.testWithNonAutowireableDataSource(JdbcTemplateAutoConfigurationTests.java:211)

There's a mismatch between @ConditionalOnBean finding a matching bean in the bean factory and autowiring then being unable to use that bean as it's not an autowiring candidate.

At this point, Spring Boot's auto-configuration isn't compatible with autowireCandidate = false and I don't think there's a workaround. For now, if you want to use auto-configuration, you'll have to avoid defining beans that are not autowire candidates if those beans are the subject of @ConditionalOnBean.

Comment From: wilkinsona

@alexey-anufriev what's your use case for an autowireCandidate = false bean in a Spring Boot app? If you're trying to hide something from other consumers, a common approach is to wrap it in a "container" type such as DefaultSockJsSchedulerContainer in Spring Framework.

Comment From: alexey-anufriev

@alexey-anufriev what's your use case for an autowireCandidate = false bean in a Spring Boot app? If you're trying to hide something from other consumers, a common approach is to wrap it in a "container" type such as DefaultSockJsSchedulerContainer in Spring Framework.

My case is very close to my initially reported code, I have a ThreadPoolTaskExecutor dedicated to a concrete purpose. I could have wrapped it and hide from possible injections in other places in the code but ThreadPoolTaskExecutor itself has a pretty complex lifecycle and I would like Spring to manage it for me. Duplicating all the interfaces and delegating to the wrapped instance would lead to quite a lot of boilerplate code.

Comment From: philwebb

We discussed this today and we consider this a bug, but one that is quite risky to fix before 3.4. We're considering adding some additional attribute to the ...OnBean condition annotations to allow autowireCandidate on the bean definition to be considered.

Comment From: alexey-anufriev

Do you have in mind how will this work? Will the new default behavior be a breaking change? I mean, for now, non-candidates are skipped by default. Will I be required to consider those, or will I be required to exclude those?

Comment From: wilkinsona

We're not yet certain and we'd like to do some experimentation. Right now, I think it's likely that it will be a breaking change and we'll start ignoring non-autowire candidates by default when looking for matching beans. We'll have to wait and see though.

Comment From: quaff

It should be backported since Bean#autowireCandidate() is not new feature of Spring Framework, WDYT @wilkinsona

Comment From: philwebb

We think it's a bit risky for a backport since it's a change in behavior.

Comment From: quaff

We think it's a bit risky for a backport since it's a change in behavior.

It's a bug fix, I can't imagine which user case would rely the buggy behavior.

Comment From: wilkinsona

It's also the risk of regression due to unexpected or accidental side-effects. Given that the problem has existed with XML config for Boot's entire life and with Java config since Boot 2.1 yet it was only reported for the first time last month, I see no need to back port the fix and risk destabilising maintenance branches.

Comment From: bmaehr

Is this the same reason why

@ConditionalOnBean(XYService.class)
@Service
public class AServiceImpl {

    public AServiceImpl (final XYService xyService) {
        this.xyService= xyService;
    }
...

}

or

@ConditionalOnBean(XYService.class)
@Service
public class AServiceImpl {

    @Autowired(required = false)
    private XYService xyService;
...

}

is causing UnsatisfiedDependencyException?

Comment From: wilkinsona

It's impossible to tell without knowing how XYService is defined. If it's defined with @Bean(autowireCandidate = false) then, yes, that would cause an UnsatisfiedDependencyException.

If you have any further questions, please follow up on Stack Overflow. As mentioned in the guidelines for contributing, we prefer to use GitHub issues only for bugs and enhancements.