Please forgive the title, I am not sure how to name it.

Consider the following @ConfigurationProperties class

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

    private List<Nested> items = new ArrayList<>();

    public List<Nested> getItems() {
        return items;
    }

    public void setItems(List<Nested> items) {
        this.items = items;
    }

    public static class Nested {

        private String irrelevant;

        private Nested nested;

        public String getIrrelevant() {
            return irrelevant;
        }

        public void setIrrelevant(String irrelevant) {
            this.irrelevant = irrelevant;
        }

        public Nested getNested() {
            return nested;
        }

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

    }

}

The Nested class contains an object of the same type of the parent class.

This does not work - items[*].nested is always null

I have tried with the following properties

example:
  items:
    - irrelevant: hello
      nested:
        irrelevant: there

I could not find anything related to this scenario in https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-Configuration-Binding but it does mention that Nested properties should be initialized using "new" operator. However, using "new" on "nested" field is not possible as it will trigger a stack overflow exception.

Additionally, mentioned documentation says:

If an intermediate relationship is null, a new instance will be created using the default constructor and the related setter will be called with it.

However, this is not happening in this case.

You can find the reproducer at https://github.com/ZIRAKrezovic/nestedbinder

Did I do something wrong, or is this another feature that I failed to find valid documentation for?

Comment From: ZERO-70

Your Nested class contains a property of its own type (Nested nested), which creates a potential for infinite recursion during initialization or property binding. This can lead to the nested field always being null. To resolve this issue, you can initialize the nested property explicitly in the Nested class. For example:

public static class Nested { private String irrelevant; private Nested nested = new Nested(); // Explicit initialization

public String getIrrelevant() {
    return irrelevant;
}

public void setIrrelevant(String irrelevant) {
    this.irrelevant = irrelevant;
}

public Nested getNested() {
    return nested;
}

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

}

If you found this explanation and contribution helpful, I would appreciate it if you could assign this contribution to me. Thank you!

Comment From: ZIRAKrezovic

Hi @ZERO-70 I have already mentioned that explicit initialization using new operator causes StackOverflowException.

Comment From: nosan

Duplicates #16444 ?

Comment From: ZIRAKrezovic

Duplicates #16444 ?

Looks like, and explanation in there is enough to make me reconsider the approach.

Comment From: nosan

@ZIRAKrezovic

You can try this tricky way:

@ConfigurationProperties("example")
public record ExampleProperties(List<Nested> items) {

    public record Nested(String irrelevant, List<Nested> nested) {

    }

}

But you have to update your YAML to:


example:
  items:
    - irrelevant: a
      nested:
        - irrelevant: b
          nested:
            - irrelevant: c
              nested:
                - irrelevant: d
                  nested:
                    - irrelevant: e
                      nested: [ ]

ExampleProperties[items=[Nested[irrelevant=a, nested=[Nested[irrelevant=b, nested=[Nested[irrelevant=c, nested=[Nested[irrelevant=d, nested=[Nested[irrelevant=e, nested=[]]]]]]]]]]]]