Configurations. Are you doing it wrong?
Let’s go over some configuration formats that most of us may be familiar with. Talking about the good old days (not really sure about “good” though), the de-facto standard configuration format in Java was properties. Wait.. This historic configuration format is still widely used nowadays. The parser (a.k.a. java.util.Properties, a.k.a. the Hashtable with load and store functions) was released since JDK 1.0. Do you know that you can parse .properties file, store them in a Properties object(Hashtable), and then export them into an XML file? It sounds promising until you see its DTD (Not Schema).
<!-- Copyright 2006 Sun Microsystems, Inc. All rights reserved.--><!-- DTD for properties -->
<!ELEMENT properties ( comment?, entry* ) >
<!ATTLIST properties version CDATA #FIXED "1.0">
<!ELEMENT comment (#PCDATA) >
<!ELEMENT entry (#PCDATA) >
<!ATTLIST entry key CDATA #REQUIRED>
Base on this DTD, the configuration is still flat. It is just …hmm… more verbose. I think it’s probably not a good idea to convert the more succinct file format into another file format that is more powerful but not using its power. XML is a good alternative because it supports types (you can even define one), and namespace (not an XML namespace but a configuration namespace via a nested structure). It’s nice if you don’t care about the verbosity and the complexity that come with XML. Anyway, if you’re going to use XML as your configuration file, just don’t use java.util.Properties. Do yourself a favor, and go for Apache commons-configuration.
What else do we have besides .properties and XML?
JSON? JSON is nice, but you may need something that you can put comments on. This format does not support comments. Putting a comment in a field as part of data is not quite practical.
They’re all awesome. It will come down to a personal preference when talking about which one is the best and what format you should use if there are no other restrictions. I personally like Lightbend config which is a configuration library for HOCON.
Let’s take a look at the simple configuration written in properties file format.
# DRY (Do Repeat Yourself)
configuration.parser = java.util.Properties
configuration.path = /usr/local/etc/...
# Array (No such thing as an array. Parse your own string)
configuration.formats = XML, PROPERTIES
Note that the properties format is perfectly legit in HOCON. Lightbend config can parse a configuration file either in properties or JSON without any problem. Let’s refactor the properties config a bit to make it looks good using HOCON as the following.
# DRY (Don't Repeat Yourself)
parser = "com.typesafe.config.ConfigFactory"
path = "/usr/local/etc/..."
formats = ["JSON", "PROPERTIES", "HOCON"]
There are a lot more things that you’ll love in HOCON (same for YAML and TOML). Just stay away from Properties if you’re going to implement something serious these days. I don’t need to go into the details on how to use these modern configuration file formats because they all have a ton of great documentation out there that are surely better than I could do.
One thing that I need to point out since we’re in the era where multi-threading systems is a norm. Properties is thread safe because it is a Hashtable. It comes with a cost though because the read method is synchronized. Put in other words, no other threads can enter the read method until one thread finishes. Well if you think that that’s bad, there is a worse issue. It’s mutable! The configuration should be a single source of truth. Thus, it must not be altered at runtime. Period.
Ok. Let’s stop trash talking about java.util.Properties. It had its days. The problem is it still does. Alright I’m stopping.. :P
My next topic will be about Java System’s Properties. You should avoid it at all costs. Ok sometimes the cost is too high to bear. Avoid it if it’s possible — or make it possible. It has every single bad trait of java.util.Propeties because it is actually a java.util.Properties in a global scale.
Avoid using System’s properties.
The followings are some reasons why you should avoid using System’s properties as your source of configuration.
- It’s slow in a multi-threading environment because Hashtable methods are synchronized.
- It’s mutable. It sounds like a good idea that you can override a configuration at runtime. It’s actually a nightmare in a large system.
- We’ve learned in programming 101 class that the global variable is bad. System properties are like global variables. Assume that you have a module that uses system properties as its configuration. You cannot have more than one instances of that module running in the same JVM with a different set of system properties sharing the same names. You need to run it in a separate VM. That module may need 30 other modules to function properly. The workaround of having a separate VM is very inefficient in this case.
- It’s very hard to test. Many test frameworks can run tests in parallel which could save a tremendous time on a regression test. How can you test your application with a different set of system properties in this case? You have to disable the parallel feature in your test framework, or it will be a tough task for you to write proper tests in that condition — or else don’t write tests at all (take this advice at your own risk).
What if you cannot avoid it?
I know it’s virtually impossible to avoid using system properties as a configuration in reality. It’s super convenient. I’d be surprised if it were not used at all. Actually, many popular and production-grade libraries and frameworks use it and they pass this obligation onto you.
However, there is a way to mitigate the issue of having configurations in system properties. First, you have to lower the number of configurations in system properties as much as possible. Basically, you just don’t add your own configurations to system properties. Second, you have to convert the configurations in system properties into a Config object (Lightbend config).
Here is a short snippet on how to do that.
Config config = ConfigFactory.load()
That’s it! The load method loads and combines the configuration in the following order: system properties, application.conf, application.json, application.properties, and reference.conf. It’s ordered by the priority as well. If you have a configuration named “format” in both system properties and application.conf, the one in system properties will be used.
Note that the document says that this method should be used by libraries and frameworks. You may want to consider another way to construct a config object.
You can customize the load order and precedence however you like:
Config config = ConfigFactory.systemProperties()
The config above will load system properties , myapp.conf file in the current directory, and myapp.conf file in the classpath respectively. Again, you can change the order any way you want. This makes it easy for testing as well because you can override anything easily. The config object is immutable. Thus, it’s thread safe without any performance penalty. It can convert system properties into a Config object. No one can mess with your system at runtime. You just have to make sure that you load the configuration before anyone else :)
While system properties is bad in many aspects, it’s useful sometimes. Just make sure keeping them minimal. My statement about avoiding it at all costs is totally exaggerating. Thank you for reading.