3

I have a configuration class like below. All of fields in the inner class OptionalServiceConfigs has a default value as annotated using @Value as shown in below.

Sometimes in my application.properties file, it does not have a single service prefixed property. In that case, we want to have loaded an OptionalServiceConfigs instance with its default field values.

@Configuration
@ConfigurationProperties(prefix = "myconf")
public class MyConfigs {

   // ... rest of my configs

   @Value("${service:?????}") // what to put here, or can I?
   private OptionalServiceConfigs service;   // this is null

   // In this class all fields have a default value.
   public static class OptionalServiceConfigs {

       @Value("${mode:local}")
       private String mode;

       @Value("${timeout:30000}")
       private long timeout;

       // ... rest of getter and setters
   }

   // ... rest of getter and setters
}

But unfortunately, the service field is null when it is accessed using its getter method. Because spring boot does not initialize an instance of it when there is no property keys found with prefixed myconf.service.* in my application.properties file.

Question:

How can I make service field to initialize to a new instance along with its specified default field values when there are no corresponding prefixed keys in properties file?

I can't imagine a value to put in annotation @Value("${service:?????}") for service field. Nothing works, tried, @Value("${service:}") or @Value("${service:new")

isuru89
  • 742
  • 11
  • 19
  • And when you *do* put a value in application.properties, it works? What value do you put in there? – rustyx Jan 14 '19 at 11:13
  • Remove `@Value` (you shouldn't combine `@ConfigurationProperties` and `@Value`!). Just create a new instance of `OptionalServiceConfigs` in your class and set the default values as regular values. If they aren't set the defaults apply then, if they are set, Spring Boot will overwrite it. – M. Deinum Jan 14 '19 at 11:19
  • @rustyx If I put `myconf.service.timeout=10000` or `myconf.service.mode=remote` or both, then `service` field is getting initialized with all of field default+specified values. The `service` field does not get initialized when I don't have any of properties. – isuru89 Jan 14 '19 at 11:27
  • @M.Deinum Actually, what you are saying is my Plan-B! :D But I am curious to know is there any spring way of doing it? I mean, can I say spring to initialize a new instance with default values in case I don't have any of nested property keys, rather than initializing and setting it myself. And also it differs from how I load my other nested configurations. – isuru89 Jan 14 '19 at 11:40
  • 1
    No there isn't because you shouldn't be mixing `@ConfigurationProperties` and `@Value`. The way I described is also the way used internally in Spring Boot to define defaults and have binding. – M. Deinum Jan 14 '19 at 11:41

2 Answers2

1

Based on @M. Deinum's advice, did some changes to configuration class. I am a newbie to Spring and it seems I have misunderstood how Spring works behind-the-scenes.

  1. First I removed all @Value annotation from inner class (i.e. OptionalServiceConfigs), and as well as service field in MyConfigs class.
  2. Then, initialized all inner class fields with their default values inline.
  3. In the constructor of MyConfigs, I initialized a new instance of OptionalServiceConfigs for the field service.

By doing this, whenever there is no service related keys in my application.properties a new instance has already been created with default values.

When there is/are service related key/s, then Spring does override my default values to the specified values in application.properties only the field(s) I've specified.

I believe from Spring perspective that there is no way it can know in advance that a referencing field (i.e. service field) would be related to the configurations, when none of its keys exist in the configuration file. That must be the reason why Spring does not initialize it. Fair enough.

Complete solution:

@Configuration
@ConfigurationProperties(prefix = "myconf")
public class MyConfigs {

   // ... rest of my configs

   private OptionalServiceConfigs service;

   public static class OptionalServiceConfigs {

       private String mode = "local";

       private long timeout = 30000L;

       // ... rest of getter and setters
   }

   public MyConfigs() {
      service = new OptionalServiceConfigs();
   }

   // ... rest of getter and setters
}
isuru89
  • 742
  • 11
  • 19
0

you can try such a structure which works for me quite fine:

@Data
@Validated
@ConfigurationProperties(prefix = "gateway.auth")
@Configuration
public class AuthProperties {

@NotNull
private URL apiUrl;

@Valid
@NotNull
private Authentication authentication;

@Data
public static class Authentication {
    @NotNull
    private Duration accessTokenTtl;
    @NotNull
    private String accessTokenUri;
    @NotNull
    private String clientId;
    @NotNull
    private String clientSecret;
    @NotNull
    private String username;
    @NotNull
    private String password;
    @Min(0)
    @NonNull
    private Integer retries = 0;
   }
}

Important is to have getters and setters in order to enable Spring to postprocess ConfigurationProperties, I am using Lombok (@Data) for this.

please see here for more details:

Baeldung ConfigurationProperties Tutorial

  • `@NotNull` caused to fail immediately as it is always null when there are no keys (`gateway.auth.authentication` in your case) in application properties file. I want to make it automatically initialize to a new instance. – isuru89 Jan 15 '19 at 03:32
  • You would configure defaults and specific value and overwrite getters for specific values to return either specific or default value. – Ulrich Kowohl Jan 15 '19 at 07:25