When using @ConfigurationProperties via either @ConfigurationPropertiesScan or @EnableConfigurationProperties, bean scope specified via @Scope is not respected. On the other hand, when defining @ConfigurationProperties with additional @Configuration/@Component/@Bean annotation, specified scope is respected

Provided below is a sample application using spring boot 3.3.2

package configbug;

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@SpringBootApplication
@EnableConfigurationProperties(ConfigBugApplication.MyProperties.class)
public class ConfigBugApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConfigBugApplication.class, args);
    }

    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @ConfigurationProperties("my.properties")
    public static class MyProperties {
        private String value;
    }

    @Component
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @ConfigurationProperties("my.other.properties")
    public static class MyOtherProperties {
        private String value;
    }

    @Configuration
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @ConfigurationProperties("my.yet.another.properties")
    public static class MyYetAnotherProperties {
        private String value;
    }

    @ConfigurationProperties("my.yet.yet.another.properties")
    public static class MyYetYetAnotherProperties {
        private String value;
    }

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    MyYetYetAnotherProperties myYetYetAnotherProperties() {
        return new MyYetYetAnotherProperties();
    }

    @EventListener
    public void ready(ApplicationReadyEvent event) {
        var applicationContext = (AnnotationConfigApplicationContext) event.getApplicationContext();
        System.out.printf("MyProperties scope = '%s'%n", applicationContext.getBeanDefinition("my.properties-configbug.ConfigBugApplication$MyProperties").getScope());
        System.out.printf("MyOtherProperties scope = '%s'%n", applicationContext.getBeanDefinition("configBugApplication.MyOtherProperties").getScope());
        System.out.printf("MyYetAnotherProperties scope = '%s'%n", applicationContext.getBeanDefinition("configBugApplication.MyYetAnotherProperties").getScope());
        System.out.printf("MyYetYetAnotherProperties scope = '%s'%n", applicationContext.getBeanDefinition("myYetYetAnotherProperties").getScope());
    }

}

It prints following result

MyProperties scope = ''
MyOtherProperties scope = 'prototype'
MyYetAnotherProperties scope = 'prototype'
MyYetYetAnotherProperties scope = 'prototype'

It seems that the following if statement in ConfigurationPropertiesScanRegistrar is causing it to behave differently

private void register(ConfigurationPropertiesBeanRegistrar registrar, Class<?> type) {
        if (!isComponent(type)) {
            registrar.register(type);
        }
}

private boolean isComponent(Class<?> type) {
        return MergedAnnotations.from(type, SearchStrategy.TYPE_HIERARCHY).isPresent(Component.class);
}

and the registrar itself registers properties with default scope.

Comment From: snicoll

This is working as designed. The Scope annotation defines how it should be used and it matches your sample above.

Comment From: wilkinsona

The javadoc of @Scope says the following:

When used as a type-level annotation in conjunction with @Component indicates the name of a scope to use for instances of the annotated type.

It's not a huge leap from there to @EnableConfigurationProperties or @ConfigurationPropertiesScan defining a bean and wanting to be able to control its scope. I think it's worth at least considering an expansion of when @Scope is honoured to include "components" that are defined through @EnableConfigurationProperties or @ConfigurationPropertiesScan.

Comment From: sfilipiak-inpost

@snicoll I see your point, that defining @ConfigurationProperties beans via @EnableConfigurationProperties or @ConfigurationPropertiesScan is not an orthodox way of defining spring framework beans. However I believe it would be great if spring boot could define those beans in a 'spring framework compatible' way.

Comment From: philwebb

Closing in favor of PR #42073. Thanks @nosan!