Why does @ConfigurationProperties need getters?

2.5k views Asked by At

I realized a strange behavior in SpringBoot.

In a yml file I have the following configuration:

main:
  record-id:
    start-position: 1
    value: 1
    enabled: true
  record-name:
    start-position: 2
    value: main
    enabled: true
  invented:
    start-position: 3
    value: 01012020
    enabled: false

And these are the classes for it:

public class FieldType {
  private Integer startPosition;
  private String value;
  private Boolean enabled;
  getters/setters
}

@Component
@ConfigurationProperties(prefix = "main")
public class Main {
  private FieldType recordId;
  private FieldType recordName;
  private FieldType invented;
  getters/setters <-- sometimes without getters
}

As you can see, the main class has @ConfigurationProperties annotation to load the properties from yml into that bean.

And here is what I have found:

  1. if I don't provide getters for the fields in the main class, then sometimes the fields in the main call stay null, so not initiated
  2. if I restart the SpringBoot, then randomly other (1 or more) fields stay null, so not initiated
  3. if I restart the SpringBoot n times, then, again and again, random fields stay null
  4. if I provide getters for the fields in the main class, then all the fields will be always instantiated from tye yml file, no matter how many times I restart SpringBoot

Why is this? Why SpringBoot requires getters for fields which represent properties in yml?

1

There are 1 answers

1
Ryuzaki L On

You don't need getter's to bind the properties, you need setters to bind properties if you are using default constructor, docs

If nested POJO properties are initialized (like the Security field in the preceding example), a setter is not required. If you want the binder to create the instance on the fly by using its default constructor, you need a setter.

In case if you are initializing the FieldType in Main class, then you don't need setters as well

@Component
@ConfigurationProperties(prefix = "main")
public class Main {
    private FieldType recordId = new FieldType();
    private FieldType recordName = new FieldType();
    private FieldType invented = new FieldType();

 }

You can also use Constructor binding by completely avoiding setters

public class FieldType {
   private Integer startPosition;
   private String value;
   private Boolean enabled;
   
   public FieldType(Integer startPosition, String value, Boolean enabled) {
         this.startPosition = startPosition;
         this.value = value;
         this.enabled = enabled

  }

  @ConstructorBinding
  @ConfigurationProperties(prefix = "main")
  public class Main {
       private FieldType recordId;
       private FieldType recordName;
       private FieldType invented;

  public Main(FieldType recordId,FieldType recordName,FieldType invented) {
       this.recordId = recordId;
       this.recordName = recordName;
       this.invented = invented;

  }

Just a note on Constructor Binding

To use constructor binding the class must be enabled using @EnableConfigurationProperties or configuration property scanning. You cannot use constructor binding with beans that are created by the regular Spring mechanisms (e.g. @Component beans, beans created via @Bean methods or beans loaded using @Import)