Skip to content

Spring Boot merge configuration properties

I’ve a use case where I need to bind configuration properties based on two prefixes, one of which is determined at runtime. Let’s say the constant prefix is foo and the runtime prefix is bar.

Given a new instance of Java Bean class FooBar, the code should bind all environment variables FOO_, then overwrite with all environment variables BAR_.

There’s a way to dynamically bind a prefix to a class, as I had stated in this ticket (sample code shown below). However, what’s missing is the merging of the results.

var bindable = Bindable.of(FooBar.class);
var properties = ConfigurationPropertySources.get(env);
new Binder(properties)
  .bind("prefix", bindable)
  .orElse(new FooBar());

Example:

public class FooBar {
    private Duration latency = Duration.ofMillis(500L);
    // other properties
    // getters, setters
}

If there are no environment variables FOO_LATENCY or BAR_LATENCY, FooBar.getLatency() is 500 ms. If only one of FOO_LATENCY and BAR_LATENCY is present, FooBar.getLatency() takes its value. If both FOO_LATENCY and BAR_LATENCY are present, FooBar.getLatency() takes the value of BAR_LATENCY.

Any idea how can this be done?

Answer

UPDATED

Just call bind again. It only assigns values that are found in the configuration properties, and last prefix bound will win, on a property-by-property basis.

Example

class FooBar {
    private String a;
    private String b = "B";
    private String c;
    private String d;
    private String e = "E";
    // Getter, setters, and toString here
}

Properties (YAML)

x.a: Hello
x.b: World
z.a: Goodbye
z.c: Test

Test

Binder binder = Binder.get(env);
FooBar fooBar = new FooBar();
System.out.println(fooBar);
fooBar = binder.bind("x", Bindable.ofInstance(fooBar)).orElse(fooBar);
System.out.println(fooBar);
fooBar = binder.bind("y", Bindable.ofInstance(fooBar)).orElse(fooBar);
System.out.println(fooBar);
fooBar = binder.bind("z", Bindable.ofInstance(fooBar)).orElse(fooBar);
System.out.println(fooBar);

Output

FooBar[a=null, b=B, c=null, d=null, e=E]
FooBar[a=Hello, b=World, c=null, d=null, e=E]
FooBar[a=Hello, b=World, c=null, d=null, e=E]
FooBar[a=Goodbye, b=World, c=Test, d=null, e=E]

As you can see, the third binding overrides the values from the first, but only for properties that are actually configured, which is why the second binding does nothing.

I also simplified the logic to skip the use of ConfigurationPropertySources.get().