Kevin
Kevin

Reputation: 1697

How can I use Optional values in spring-boot configuration properties?

Using spring-boot v3.2.5 I've got configuration that deserializes to this class:

@Getter
// Intentionally Validated and not jakarata.validation.Valid because Valid does not trigger
// validation for configuration properties, and I don't know why.
@Validated
@ConfigurationProperties(prefix = "pfx")
public class TenantsConfiguration {

  @NotNull private final Map<String, TenantConfiguration> tenants;

  @ConstructorBinding
  public TenantsConfiguration(Map<String, TenantConfiguration> tenants) {
    this.tenants = tenants;
  }

  @Getter
  public static class TenantConfiguration {
      @NotNull private final Optional<String> field1;
      @NotNull private final Optional<String> field2;

      @ConstructorBinding
    public TenantConfiguration(
        Optional<String> field1, Optional<String> field2) {
        this.field1 = field1;
        this.field2 = field2;
      }
  }
}

And a config that looks like:

pfx:
  tenants:
    t1:
      field1: abc

The application fails to start because field2 is null instead of Optional.empty():

Binding to target TenantsConfiguration failed:

    Property: pfx.tenants.t1.field2
    Value: "null"
    Reason: field2 must not be null

During deserialization from api requests, spring handles null -> Optional conversion just fine. I tried converting TenantConfiguration to a record, but that did not change anything. How do I get the normal null -> Optional conversion when I'm loading configuration in spring-boot?

Upvotes: 1

Views: 152

Answers (1)

hitesh
hitesh

Reputation: 177

Optional is usually not recommended for ConfigurationProperties. This is a known behaviour which spring-boot marked as 'not a bug, but a feature'.

Source : https://github.com/spring-projects/spring-boot/issues/21868

Checkout these comments specifically :

So a work around for your problem would be :

@Getter
public static class TenantConfiguration {
    @NotNull private Optional<String> field1;
    @NotNull private Optional<String> field2;

    @ConstructorBinding
    public TenantConfiguration(String field1, String field2) {
      this.field1 = Optional.ofNullable(field1);
      this.field2 = Optional.ofNullable(field2);
    }
}

Upvotes: 4

Related Questions