Hierarchical Java Configuration

Hard-coding secrets in application code is not ideal.

It makes rolling them hard by coupling them to an artifact, and is not particularly secure, if the artifact itself is compromised.

It was, once-upon-a-time, the easiest way to get the job done. Saddling us with technical debt, which needed to be paid back.

How can migrate these secrets into configuration files, environment variables, or a third-party service, in a safe and incremental way?

We started building java-config-fallback as a way of facilitating this process, and decided to build it in the open from Day 1.

How it works

This library exposes an entry-point with Configuration.of(...) and accepts any number of ConfigurationSource parameters.

Configuration config = Configuration.of(
environment(),
properties("/etc/myapp/config.properties")
);

Values can be retrieved with .get(String key), returning an Optional<String> which represents a potentially missing value (for example, if the key is not present in any of the sources)

config.get("some-key"); // Optional<String>

The input sources are composed together such that querying for a key returns the first corresponding value it finds (in the order passed to the .of(...) method), falling back to successive sources if not found.

Design Decisions

There are already libraries (such as https://github.com/ufoscout/properlty) that do something very similar — why build our own?

  • Focus on cleanliness and simplicity: at time of writing, the core library is less than 100 lines of code.
  • Architecting for extensibility: the ConfigurationSource entry point is used to retrieve a specific value for a key, not to load all possible values into memory. This opens the door to providers for external services that operate on a request-by-request basis.
  • Use modern Java features: we designed ConfigurationSource as a functional interface, so custom providers can be defined in-line as lambda expressions:
Configuration config = Configuration.of(
(key) -> "always-this-value"
);
  • Short-circuit querying: Incorporating .orElseGet(() -> ...) into the composition of the sources stops the query being evaluated if a value is found “higher up”. For services where the call is non-trivial or expensive, this is much more efficient.
  • Do one thing well: The library doesn’t coerce types (opting for String key, String value) or assume behaviour if a source cannot be queried (missing file or slow network call). This is up to the user to decide, and simplifies a lot of our other design choices.
  • Eat our own dog-food: This library is in use at Unruly, helping us transition our app configuration and advance our architecture. We don’t want to release anything we wouldn’t use ourselves.

What’s coming later

Adding some new ConfigurationSources is something we want to achieve before the big 1.0.0 release, such as:

  • New file types — XML, JSON, …
  • 3rd party services — Vault, Consul, AWS Parameter Store
  • Local services — data stores, AWS instance metadata

Tracking where a given property came from, to confirm that properties are being loaded from the specified place (https://github.com/unruly/java-config-fallback/issues/1)