A ConfigurationProperties class with a recursive subclass will not bind the recursive properties. I have tested this with Spring Boot 2.1.3.

This issue can be reproduced using the following application.properties file:

test.x.value: "Test1"
test.x.test.value: "Test2"
test.x.test.test.value: "Test3"

I would expect this application.properties to be bound to the TestProperties bean as TestProperties{x=Test{value='Test1', test=Test{value='Test2', test=Test{value='Test3', test=null}}}}, but Spring is only creating this: TestProperties{x=Test{value='Test1', test=null}}

Here is the ConfigurationProperties class (enabled with@EnableConfigurationProperties({TestProperties.class})):

@ConfigurationProperties(prefix = "test")
public class TestProperties {

    static class Test {

        private String value;
        private Test test;

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }

        public Test getTest() {
            return test;
        }

        public void setTest(Test test) {
            this.test = test;
        }

        @Override
        public String toString() {
            return "Test{" +
                    "value='" + value + '\'' +
                    ", test=" + test +
                    '}';
        }
    }

    private Test x;

    public Test getX() {
        return x;
    }

    public void setX(Test x) {
        this.x = x;
    }

    @Override
    public String toString() {
        return "TestProperties{" +
                "x=" + x +
                '}';
    }
}

In addition, the spring boot configuration processor does not support this class, it will throw a StackOverflowException.

See https://stackoverflow.com/questions/52906556/spring-configuration-properties-recursive-type

Comment From: mbhave

This was done as stack-overflow protection which is required when binding a bean to a non enumerable source. I wonder if we can allow recursive binding for beans when the source is iterable.

/cc @philwebb

Comment From: snicoll

In addition, the spring boot configuration processor does not support this class, it will throw a StackOverflowException.

What metadata do you expect with such setup anyway? I agree the SO is far from ideal (feel free to create a separate issue for it) but the metadata represents a finite set of information, it's not "dynamic".

Comment From: philwebb

I'm not really sure that we should attempt to support such a setup, it feels like we'd be introducing a lot of additional complexity in the binder.

@engineal what kind of data are you trying to represent in your properties?

Comment From: Naeramarth

I am developing a SPA, which I'm serving with Spring and Thymeleaf. The SPA itself is highly customizable through the configuration file. I'm using the @ConfigurationProperties to parse the configuration as a java object and serve it through a REST endpoint to the SPA.

I have a class, (lets call it "objectA", its only a small part of the configuration) which includes itself three times and a list, like this:

public class ObjectA{
    private ObjectA and;
    private ObjectA or;
    private ObjectA xor;
    private List<ObjectB> something;
}

In the SPA I'm trying to find a true/false value, according to the conditions represented in an object of this class. Individual conditions are represented as ObjectB in this example. These conditions can be combined with all the available logic operators (and, or and xor). Conditions can also be nested within each other, for more complex data structures. In the end it can look like this (ObjectB is simplified to the boolean result, to make it easier):

and: 
 or: 
  and: 
   something: 
    - true 
  something:
   - true
   - false
   - false
 xor:
  something: 
   - true
   - true
 something: 
  - true
  - true
  - true

In this case the condition would be evaluated to "false", because "and.xor" would result to "false" (all other nested conditions result to true).

I know I could realize pretty much the same structure with an object like this:

public class ObjectA{
    private String logicOperator;           //this can be "and", "or" or "xor"
    private List<ObjectA> nestedAs;
    private List<ObjectB> something;
}

But I'd prefer the first way, as it will result in faster execution (the amount of analysed data can get very big), as I won't have to compare Strings each time, but only check if a nested object is present at all. Also, having objectA in a list allows for the same logic operators to be present multiple times within the same object, which would require more iterations through the list (nestedAs) and result in a more messy configuration file.

In my case, its not critical to include recursive ConfigurationProperties, since I have other ways to solve it, but there could be other instances where they are less avoidable.

Comment From: engineal

Thanks for providing the example @Naeramarth. My use case is actually very similar.

Comment From: philwebb

We've discussed this again today as a team and the general consensus is that we don't want to support recursive property binding. We can't really see a clear way to implement such a feature without adding a lot of complexity.

@engineal and @Naeramarth, for your specific use-cases I would investigate other binding options. Perhaps using a JSON or XML format might allow you to use other libraries that don't have any recursive binding restrictions.

Comment From: brucelwl

@engineal @wilkinsona @philwebb Maybe you can solve your problem by using the extends keyword, SpringBoot Recursive ConfigurationProperties class doesn't bind recursive properties SpringBoot Recursive ConfigurationProperties class doesn't bind recursive properties SpringBoot Recursive ConfigurationProperties class doesn't bind recursive properties SpringBoot Recursive ConfigurationProperties class doesn't bind recursive properties