Springboot relaxed binding with maps is duplicating properties

Viewed 426

I've got an application.yml that looks something like this:

root:
  mapkey:
    name-with-dashes:
      vara: some value 1
      varb: 1
    different-name-with-dashes:
      vara: some value 2
      varb: 2

I am using the @ConfigurationProperties annotation to inject a config POJO that matches this structure. It looks basically like this (removed constructors etc. for readability, but just know that it works):

@Configuration
@ConfigurationProperties("root")
public class ParentConfig {
    private Map<String, ChildConfig> mapkey;
}
public class ChildConfig {
    private String vara;
    private Integer varb;
}

Locally when testing this, I have no issues and the injection works fine.

However, I run into an issue when trying to deploy and override these fields with system variables. From the Springboot Documentation, it says that the system properties should be created by converting the periods to underscores, removing the dashes, and converting to uppercase. In my case then I end up with variables like this:

ROOT_MAPKEY_NAMEWITHDASHES_VARA: deployment value 1
ROOT_MAPKEY_NAMEWITHDASHES_VARB: 6
ROOT_MAPKEY_DIFFERENTNAMEWITHDASHES_VARA: deployment value 2
ROOT_MAPKEY_DIFFERENTNAMEWITHDASHES_VARB: 12

However, when I run the application with these variables and inspect the ParentConfig object, the map looks like this:

{
    mapkey: {
        name-with-dashes: {
            vara: deployment value 1
            varb: 6
        }, 
        namewithdashes: {
            vara: deployment value 1
            varb: 6
        }, 
        different-name-with-dashes: {
            vara: deployment value 2
            varb: 12
        }, 
        differentnamewithdashes: {
            vara: deployment value 2
            varb: 12
        }
    }
}

It's duplicating the properties with and without dashes and I have no idea why. And not just that, but all of the values have the deployment values, not the application.yml values. Very confusing.

1 Answers

Recently, I ran into a similar problem with "relaxed binding". My problem was that my internal config didn't get overwritten by the external config, where the internal config is an exact match and the external config is not. So there is a similarity here but not exactly as yours. However, when I change the external config to be the exact match, it overwrites.

I haven't found any documentation about this yet but I'm fairly sure this is a priority issue and how "relaxed" the binding actually is. In my case, it totally makes sense that the internal exact matching took priority and the "relaxed" external one (an old typo) is discarded.

To answer your question, since your object is a map, where the pairs don't start with strict keys. That makes it even fuzzier (if not impossible) to overwrite. My thoughts are that it couldn't differentiate between the new keys and the keys to overwrite unless it's an exact match. Thus, it makes sense for the new system env-vars to be inserted as new keys.

Now you probably will ask how would you make it exact in this case. Well, you can't, unless you change your internal config to follow the system properties naming rules. Or you can have external config file that has the exact naming.

Overall, I think "relaxed binding" is more like a fall back option. It shouldn't be your first choice. Try using exact naming when you can. I'd say that this feature is probably more helpful when an old project upgrading to a new version and the devs wouldn't have to go through tons of config files to change names.

Related