Changing System Properties in Isolation

November 25, 2019   ·   3 min read

java system properties integration 3rd party

So the other day I was working with a 3rd party library which used System properties as their feature flags. So before calling anything in that library, you’d have to setup the proper system properties. While some of them were specific to the library, the library also did depend on some generally useful system properties (e.g. user.home to write settings).1
Now, most engineers would nice encapsulate this behind a Facade and use a try/finally to manage the system properties, something along the lines of this snippet:

String oldValue = System.getProperty("someSystemProp");
try {
    System.setProperty("someSystemProp", "customValue");
    // ... call into 3rd party library
} finally {
    System.setProperty("someSystemProp", oldValue);
}

While this is neat and gets the job done, it only solves the problem in a very limited way. Given the global nature of system properties, any other thread running in parallel will now see the customValue as there is no isolation between this code and the rest of the system. Even worse, someone else might change the value of one of the properties while we’re calling the 3rd party library. Assuming you can control all places that use those system properties, you could introduce a lock and ensure every calling code goes through that. While that works, it has the downside of having an exclusive blocking behavior as you can never run any code dealing with the 3rd party dependency yourself.2

Scoped System Properties to the rescue.
Scoped system properties solves the dilemma mentioned above by providing an isolated environment for system properties to be read and written without any other part of your application to see it. While this is what you’d already get by refactoring towards a better abstraction, Scoped System Properties hides this behind the regular system property API (System.getProperty(key), System.setProperty(key, value), …).

This allows 3rd party code to run unmodified while we can provide proper isolation of said system properties. Lets take the example from above:

System.setProperty("someKey", "global value");
try (PropertyEnvironment env = ScopedSystemProperties.newPropertyEnvironment()) {
    env.setProperty("someKey", "scopedValue");
    // or using the usual Java APIs
    System.setProperty("someKey", "scopedValue");

    System.getProperty("someKey"); // => "scopedValue"

    // another thread calling System.getProperty("someKey") => global value
}
System.getProperty("someKey") // => "global value"

Above, we used scoped-system-properties (available via maven central) in our regular production code. In case you need system property isolation in unit tests, you can also use scoped-system-properties-junit. The JUnit 5 extension offers @IsolatedSystemProperties to setup and teardown the property environment before/after the test. This allows running tests in parallel without an exclusive lock being present.

Head over to GitHub to find the Maven/Gradle coordinates, find more documentation or report any issues you find.


  1. I’m talking here about a library that is outside of our control. If we do control the library, I highly suggest to refactor it away from globals like system properties by injecting any configuration from the outside. ↩︎

  2. But to be fair, depending on your use case, this might already be enough. If you happen to have such a problem in your tests, have a look at JUnits @ResourceLock (see Synchronization when running Parallel Tests). While this has the same effect of avoiding race conditions on the system properties, such tests cannot be run in parallel if they require mutating access to system properties. ↩︎



comments powered by Disqus