Using Spring Boot 3.0.1, I have the following POJO on which I wish to bind a certain property:

public class TestProperties {

    private String prop1;

    private String prop2;

    public TestProperties(String prop1, String prop2) {
        this.prop1 = prop1;
        this.prop2 = prop2;
    }

    public String getProp1() {
        return prop1;
    }

    public void setProp1(String prop1) {
        this.prop1 = prop1;
    }

    public String getProp2() {
        return prop2;
    }

    public void setProp2(String prop2) {
        this.prop2 = prop2;
    }

}

Note that this is a class from a third-party library not under my control. I need to make this available as a Bean, and I wish to set a property on it:

    # (in application.properties) testprop.prop1=something

    @Bean
    @ConfigurationProperties(prefix = "testprop")
    public TestProperties testProperties() {
        return new TestProperties("1", "2");
    }

The previous code fails silently, and my property is not set.

It seems as if the new Spring Boot 3 rules regarding automatic ConstructorBinding have something to do with this. If I remove the parameterized constructor from the TestProperties class, or if I add a second constructor, the problem goes away and my property is set correctly. However, since I return an already constructed object from the Bean method, I would expect Spring Boot to always use setter binding instead of constructor binding.

(This is a simplified example. In reality, TestProperties is constructed using a builder, and the parameterized constructor is package private, intended to be used only by that builder.)

Comment From: wilkinsona

Minimal repro:

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

@EnableConfigurationProperties
public class Gh33710Application {

    @Bean
    @ConfigurationProperties("testprop")
    TestProperties testProperties() {
        return new TestProperties("1", "2");
    }

    public static void main(String[] args) {
        String prop1 = SpringApplication.run(Gh33710Application.class, "--testprop.prop1=something").getBean(TestProperties.class).getProp1();
        System.out.println(prop1);
    }

    static class TestProperties {

        private String prop1;

        private String prop2;

        public TestProperties(String prop1, String prop2) {
            this.prop1 = prop1;
            this.prop2 = prop2;
        }

        public String getProp1() {
            return prop1;
        }

        public void setProp1(String prop1) {
            this.prop1 = prop1;
        }

        public String getProp2() {
            return prop2;
        }

        public void setProp2(String prop2) {
            this.prop2 = prop2;
        }
    }

}

The ConfigurationPropertiesBindingPostProcessor correctly determines that the bind method should be JAVA_BEAN and calls the binder. The binder then incorrectly performs constructor binding and returns a new, constructor-bound instance which the post-processor ignores.

The problem can be worked around by adding @Autowired to the constructor of TestProperties. This prevents constructor binding, allowing Java bean-based binding to be used instead. Other than that, it has no effect. It is rather unintuitive though as the constructor will never actually be autowired as it's only ever called by application code.

Comment From: StijnArnauts

Thank you for the workaround. Unfortunately, in my case TestProperties is a third party class which I cannot change. I've removed the use of ConfigurationProperties alltogether for now.