Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@ConfigurationProperties with initialized nested record properties values no longer bind #34407

Closed
winne42 opened this issue Feb 28, 2023 · 2 comments
Labels
type: regression A regression from a previous release
Milestone

Comments

@winne42
Copy link

winne42 commented Feb 28, 2023

Note: This issue is caused by the solution to the very similar issue #33409 (see below) --> I tried to write this description in the same style.

We are in the process of migrating to Spring Boot 3.0 and we noticed that nested record properties with default values can no longer be bound.

Given a configuration class...

@Configuration
@ConfigurationProperties(prefix = "example")
public class ExampleProperties {

    @NestedConfigurationProperty
    private NestedProperty nested = new NestedProperty("Default", "default");

    public NestedProperty getNested() {
        return nested;
    }

    public void setNested(NestedProperty nested) {
        this.nested = nested;
    }
}

... and the NestedProperty, which is a record:

public record NestedProperty(String name, String value) {}

Test (using Spock):

@SpringBootTest(properties = ['example.nested.name=testName', 'example.nested.value=testValue'])
class DemoApplicationSpec extends Specification {

    @Inject
    ExampleProperties exampleProperties

    def 'TestProperties'() {
        expect:
        exampleProperties.nested.name() == 'testName'
        exampleProperties.nested.value() == 'testValue'
    }
}

The test runs successfully using Spring Boot 2.7.8, but fails on 3.0.2. The reason is this if-statement that was introduced by @philwebb with this commit to fix gh-33409. The idea for this change was to force setter binding instead of constructor binding for JavaBeans if the target value already exists (by throwing away the "deduced bind constructor"), to match the JavaBeans binding behavior in Spring 2.x.

Unfortunately, this change does not really make sense for records, as records are immutable and hence setter binding cannot be used. I would expect Spring 3.x to distinguish between the two use-cases (setter binding for JavaBeans vs. constructor binding for records) just like Spring 2.x did.

Workaround until this is fixed

As a workaround, using constructor binding for records can be enforced using @ConstructorBinding on a (theoretically redundant) additional constructor:

public record NestedProperty(String name, String value) {

    @ConstructorBinding
    public NestedProperty(String name, String value) {
        this.name = name;
        this.value = value;
    }
}

Now, the binding constructor is not considered "deduced" any more and is used as expected.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Feb 28, 2023
@philwebb philwebb added type: regression A regression from a previous release and removed status: waiting-for-triage An issue we've not yet triaged labels Feb 28, 2023
@philwebb philwebb added this to the 3.0.x milestone Feb 28, 2023
@philwebb philwebb changed the title Nested record properties with default values can no longer be bound in Spring Boot 3.0 Nested record properties with instance values no longer bind Feb 28, 2023
@philwebb philwebb changed the title Nested record properties with instance values no longer bind @ConfigurationProperties with initialized nested record properties values no longer bind Feb 28, 2023
@philwebb philwebb modified the milestones: 3.0.x, 3.0.4 Feb 28, 2023
@philwebb
Copy link
Member

Thanks for the detailed analysis, it really helped!

@winne42
Copy link
Author

winne42 commented Mar 1, 2023

Wow, that was blazingly fast! Thanks so much for your work, @philwebb ! ♥

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: regression A regression from a previous release
Projects
None yet
Development

No branches or pull requests

3 participants