I have a record NestedProperties which is a member of a Configuration as follows. I want to directly inject NestedProperties and hence I am trying to declare it as a bean.

@ConfigurationProperties("app")
public record AppProperties(NestedProperties nested) {

  @Bean
  @Override
  @ConfigurationProperties("nested")
  public NestedProperties nested() {
    return nested;
  }
}

Launching the Spring Boot application results in the following exception.

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'nested' defined in com.example.demo.AppProperties: Initialization of bean failed; nested exception is java.lang.IllegalStateException: Cannot bind @ConfigurationProperties for bean 'nested'. Ensure that @ConstructorBinding has not been applied to regular bean
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:628) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1391) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1311) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791) ~[spring-beans-5.3.22.jar:5.3.22]
    ... 19 common frames omitted
Caused by: java.lang.IllegalStateException: Cannot bind @ConfigurationProperties for bean 'nested'. Ensure that @ConstructorBinding has not been applied to regular bean
    at org.springframework.util.Assert.state(Assert.java:76) ~[spring-core-5.3.22.jar:5.3.22]
    at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.bind(ConfigurationPropertiesBindingPostProcessor.java:86) ~[spring-boot-2.7.2.jar:2.7.2]
    at org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.postProcessBeforeInitialization(ConfigurationPropertiesBindingPostProcessor.java:78) ~[spring-boot-2.7.2.jar:2.7.2]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:440) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1796) ~[spring-beans-5.3.22.jar:5.3.22]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:620) ~[spring-beans-5.3.22.jar:5.3.22]
    ... 29 common frames omitted

Minimal Reproducible Example

https://github.com/naiyerasif/configuration-properties-issue/commit/47defdbcd0a019b359556d57be866d485872fb19

Additional details

If I convert the NestedProperties to regular class, things work as expected.

Version details

This is happening with Spring Boot 2.7.2 and Java 17.

Comment From: snicoll

@naiyerasif what is this @Bean on your "nested" record? A @ConfigurationProperties-annotated type is not a @Configuration.

Comment From: naiyerasif

@snicoll The @Bean is @org.springframework.context.annotation.Bean (see reproducible example). I am trying to initialize a NestedProperties bean so that instead of injecting AppProperties and calling appProperties.nested(), I can simply inject NestedProperties directly wherever I need it.

I am trying to initialize a NestedProperties bean so that instead of injecting AppProperties and calling appProperties.nested(), I can simply inject NestedProperties directly wherever I need it.

It works as expected if I declare NestedProperties as a regular Java class as follows but throws the exception (pasted in the issue description above) when it is declared as a record.

public class NestedProperties {

  private final String ext;

  public NestedProperties(String ext) {
    this.ext = ext;
  }

  public String ext() {
    return ext;
  }
}

Comment From: snicoll

Yes, I am aware of what @Bean is. Nested configuration properties are not meant to be injected, they are part of the main object that defines it. I've pushed a fix to your sample project to show you how it's supposed to be used: https://github.com/naiyerasif/configuration-properties-issue/pull/1

Comment From: naiyerasif

@snicoll I am dealing with a usecase where I've a lot of deeply nested properties which are directly injected as beans throughout the codebase. This is actually a discrepancy in existing behavior since the nested properties declared in a class work properly as beans while the same properties declared in a record don't.

Nested configuration properties are not meant to be injected.

By this comment, I believe the above behavior was a hack. Thanks for this clarification.