Hierarchical Properties for Java
Written by Andrew Lalis at 2 September, 2021
A quick overview of Java's properties for hierarchical config.
When building an application that'll be deployed somewhere on a server, you'll want to keep your secret config values separate from the more mundane options, so you likely have a separate place where you put your sensitive info, and have the application read that.
However, this adds extra baggage that makes it more difficult to quickly change things, for example quickly swapping out your database or enabling a flag to debug something on production.
Large frameworks like Spring Boot manage various property sources for you, but sometimes you don't want to include a behemoth dependency in your otherwise lightweight application. So what can you do to clean up your app's config?
Properties
Java's Properties are a great way to store configuration options, and in fact, most Java libraries use either this, or XML for user-configurable values. According to the official documentation, we can do the following to load properties from several different locations at once:
Properties props = new Properties();
props.load(new FileInputStream("file1.properties"));
props.load(new FileInputStream("file2.properties"));
props.load(new FileInputStream("file3.properties"));
With this approach, we say that properties in file2.properties
override those in file1.properties
, which is good for giving our production configuration precedence over any default values we might have packaged into the classpath.
Flexibility
However, we're still hard-coding the names of the files here. For relatively small projects, you can go ahead and stop here, but when configuration may be done in more than one place, and many people are involved in the project, you'll want to abstract the above sample a bit.
public class MultiProperties extends Properties {
public MultiProperties(Path... paths) {
for (Path path : paths) {
Properties props = new Properties();
try (FileInputStream fis = new FileInputStream(path.toFile())) {
props.load(fis);
} catch (IOException e) {
System.err.println("Could not load properties from path: " + path + ", Exception: " + e.getMessage());
}
this.putAll(props);
}
}
}
MultiProperties
as an abstraction over loading a list of properties files.
By accepting a variadic paths
variable, we're free to use whatever technique we like for finding paths to properties files, such as walking the sibling files and directories of the program's current working directory, or looking in the classpath.
Now instead of having to make a commit to update a hard-coded file name somewhere, it's a piece of cake to simply add a new file in a location that your properties loader expects, to have it override any properties found earlier.