Dear team, there is an incorrect behavior of @ConfigurationProperties when there is a field of type Map<KEY, VALUE> with VALUE being some custom object class. In this case the field will not be bound to corresponding application properties.

Link to github repo with code

How to reproduce (2.7.5 version)

Consider the following yaml:

test:
  configs:
    a:
      id: 0
      hostname: localhost
    b:
      id: 0
      hostname: 127.0.0.1

Consider the following property class and custom value class (lombok used) :

@AllArgsConstructor
@Getter
@Setter
public class Config {
  private int id;
  private String foo;
}

@ConfigurationProperties(prefix = "test")
@AllArgsConstructor
@Getter
@Setter
@ToString
public class NonWorkingTestProperties {

    private Map<String, Config> configs;
}

Resulted configs value is empty map (it will be null if there is no @AllArgsConstructor on the class

Workaround

Everything works fine if we add @ConstructorBinding annotation and @AllArgsConstructor:

@ConfigurationProperties(prefix = "test")
@ConstructorBinding
@AllArgsConstructor
@Getter
@Setter
@ToString
public class WorkingTestProperties {

    private Map<String, Config> configs;
}

Comment From: philwebb

I think the problem here is that you Config and NonWorkingTestProperties classes are both missing a default constructor. If you add @NoArgsConstructor then the binder can create the object and bind the values.

If you want to go the immutable route, in Spring Boot 2.7 you need the @ConstructorBinding annotation on either the constructor or the class. You should also drop @Setter if you decide to go that route.