Version information

Spring-boot: 2.2.6.RELEASE

Reproduction

I have an immutable @ConfigurationProperties class with nested properties:

package com.example.demo;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
import org.springframework.boot.context.properties.bind.DefaultValue;

@ConfigurationProperties
@ConstructorBinding
public class NestedConfigurationProperties {

    private final NestedProperties nested;
    private final String property;

    public NestedConfigurationProperties(
            NestedProperties nested,
            String property) {
        this.nested = nested;
        this.property = property;
    }

    public NestedProperties getNested() {
        return nested;
    }

    public String getProperty() {
        return property;
    }

    public static class NestedProperties {
        private final String nestedProperty;

        public NestedProperties(@DefaultValue("default_value") String nestedProperty) {
            this.nestedProperty = nestedProperty;
        }

        public String getNestedProperty() {
            return nestedProperty;
        }
    }
}

When I bind the class without specifying a value for the nested property the nested properties holder object is null. A test illustrating my expectations:

package com.example.demo;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;

public class PropertiesTest {

    @Test
    void nestedPropertiesAreNotNull() {
        Map<String, String> properties = new HashMap<>();
        properties.put("property", "some value");
        Binder binder = new Binder(new MapConfigurationPropertySource(properties));

        BindResult<NestedConfigurationProperties> bindResult =
                binder.bind("", Bindable.of(NestedConfigurationProperties.class));

        assertThat(bindResult.get().getNested()).isNotNull();
    }
}

Unless I set a value for a nested property (properties.put("nested.nested_property", "some value")) the NestedConfigurationProperties#nested field is null.

Expected result

The nested properties object is never null when binding the top-level properties.

Comment From: mbhave

This is a duplicate of #18917. The fix for that will be available in Spring Boot 2.2.7. An empty @DefaultValue on the constructor parameter indicates the property should never be null even when nothing was bound to it. You can give it a try with the 2.2.7 snapshots.

Comment From: jannis-baratheon

Thanks @mbhave.