Imagine you have the following configuration and the associated properties:

MyConfiguration.java
@Configuration
@EnableConfigurationProperties(MyProperties.class)
public class MyConfiguration {

  @Configuration
  @ConditionalOnProperty(value = "my.enabled", havingValue = "true")
  static class MyEnabledConfiguration {

    @Configuration
    @ConditionalOnProperty(value = "my.version", havingValue = "v1")
    static class MyEnabledFirstConfiguration {
      @Bean
      public MyClient firstClient() {
        return new FirstClient();
      }
    }

    @Configuration
    @ConditionalOnProperty(value = "my.version", havingValue = "v2", matchIfMissing = true)
    static class MyEnabledSecondConfiguration {
      @Bean
      public MyClient secondClient() {
        return new SecondClient();
      }

    }
  }

  @Configuration
  @ConditionalOnProperty(value = "my.enabled", havingValue = "false", matchIfMissing = true)
  static class MyDisabledConfiguration {

    @Bean
    public MyClient thirdClient() {
      return new ThirdClient();
    }
  }
MyProperties.java
@Getter
@Setter
@Validated
@ConfigurationProperties(prefix = "my")
public class MyProperties {

  private boolean enabled;

  private Version version = Version.V1;

  public enum Version {
    V1,
    V2
  }
}

In following case I expect the only single bean of type ThirdClient

my:
  enabled: false
  version: v1

And it actually works fine, if you write your test as following:

@SpringBootTest(classes = MyConfiguration.class)
class MySpringBootTest {

    @Autowired
    private ApplicationContext context;

    @Test
    void contextLoads() {
        assertThat(context.getBeansOfType(MyClient.class).size()).isEqualTo(1);
        assertThat(context.getBean(MyClient.class)).isInstanceOf(ThirdClient.class);
    }
}

However if you are using component scan, the parent conditional is ignored.

+ @SpringBootTest
- @SpringBootTest(classes = MyConfiguration.class)
class MySpringBootTest {
    ...
}

In this case you get the following two beans instead of expected single one as in example above. * FirstClient * ThirdClient

Comment From: raddatzk

Seems like @ComponentScan also accepts nested @Configuration classes. Maybe doing an additional check if the candidate has a parent conditional @Configuration before parsing the candidate?

Comment From: wilkinsona

The behavior of condition evaluation during component scanning is out of Spring Boot's control as it's determined by Spring Framework. Here's a minimal example of the behavior that you have described that doesn't use Spring Boot:

package com.example;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.type.AnnotatedTypeMetadata;

@Configuration
public class MyConfiguration {

    @Configuration
    @Conditional(Disabled.class)
    static class DisabledConfiguration {

        @Configuration
        @Conditional(Enabled.class)
        static class FirstClientConfiguration {

            @Bean
            public MyClient firstClient() {
                return new FirstClient();
            }

        }

        @Configuration
        @Conditional(Disabled.class)
        static class SecondClientConfiguration {

            @Bean
            public MyClient secondClient() {
                return new SecondClient();
            }

        }

    }

    @Configuration
    @Conditional(Enabled.class)
    static class ThirdClientConfiguration {

        @Bean
        public MyClient thirdClient() {
            return new ThirdClient();
        }

    }

    static class Enabled implements Condition {

        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            return true;
        }

    }

    static class Disabled implements Condition {

        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            return false;
        }

    }

    public static interface MyClient {

    }

    public static class FirstClient implements MyClient {

    }

    public static class SecondClient implements MyClient {

    }

    public static class ThirdClient implements MyClient {

    }

}
package com.example;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

import com.example.MyConfiguration.MyClient;
import com.example.MyConfiguration.ThirdClient;

@SpringJUnitConfig(MyConfiguration.class)
public class ContextConfigurationTests {

    @Autowired
    ApplicationContext context;

    @Test
    void contextLoads() {
        assertThat(context.getBeansOfType(MyClient.class).size()).isEqualTo(1);
        assertThat(context.getBean(MyClient.class)).isInstanceOf(ThirdClient.class);
    }

}
package com.example;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;

import com.example.ComponentScanTests.EnableComponentScan;
import com.example.MyConfiguration.MyClient;
import com.example.MyConfiguration.ThirdClient;

@SpringJUnitConfig(EnableComponentScan.class)
public class ComponentScanTests {

    @Autowired
    ApplicationContext context;

    @Test
    void contextLoads() {
        assertThat(context.getBeansOfType(MyClient.class).size()).isEqualTo(1);
        assertThat(context.getBean(MyClient.class)).isInstanceOf(ThirdClient.class);
    }

    @ComponentScan("com.example")
    static class EnableComponentScan {

    }

}

We'll transfer this issue to the Spring Framework issue tracker so that the Framework team can take a look.

Comment From: jhoeller

As mentioned on #30750, the classpath scan finds the nested classes directly rather than through their containing class. As a consequence, it processes them in the order that it found them in the classpath. Through declaring those nested classes as non-static, classpath scanning does not consider them as independent anymore, so they will actually be processed through their containing class then - with the order for nested classes respected there. You can achieve the same effect by removing the @Configuration stereotype from the nested classes so that classpath scanning does not identify them anymore; this works with static classes as well since they will only be found through their containing class then.