How can I iterate over (system) properties in a thread safe manner?

Viewed 2193

The following piece starts throwing ConcurrentModificationException in my application (in the iterator):

final Map<?, ?> systemProperties = System.getProperties()
final Map<String, String> properties = (Map<String, String>) systemProperties

for (final Entry<String, String> entry : properties.entrySet()) { // exception here
    System.out.println(entry)
}

I am running a multi-threaded application, and unfortunately I do not have access to the code that is modifying the system properties (it could even be a third party library).

2 Answers

To fix that, we can take a snapshot of system property keys:

final Properties systemProperties = System.getProperties()
final Set<String> keys = systemProperties.stringPropertyNames()

for (final String key : keys) {
    System.out.println("key: " + key)
    final String value = systemProperties.getProperty(key)
    System.out.println("value: " + value) // value can be null!
}

Notice the value comment - while stringPropertyNames states set of keys in this property list where the key and its corresponding value are strings, the system property could have been changed in the meantime.


Why so much legwork?

System properties are an instance of java.util.Properties, and its methods getProperty, setProperty are thread-safe.

Unfortunately, Properties' entry set's iterator (which I had used in question) is not thread-safe:

If the map is modified while an iteration over the set is in progress (except through the iterator's own remove operation, or through the setValue operation on a map entry returned by the iterator) the results of the iteration are undefined

So actually when I was iterating over that map, some system property was modified (= that entry said was modified), which caused CME to be thrown.


This q&a pair also applies to any generic Properties usage - just system properties make it trickier, with ability to access them directly with statics such as java.lang.System.setProperty(String, String) - so controlling all accesses (especially in shared code) gets harder.

You could wrap your properties in a ConcurrentHashMap so that any of your compound actions such as iteration, navigation, check-and-act, etc.. will be thread-safe. e.g.

ConcurrentHashMap<String, String> props = new ConcurrentHashMap<>(
    (Map<String, String>)(Object)System.getProperties());

for(Map.Entry<String, String> entry: props.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

Note that the iterators returned by the ConcurrentHashMap are weekly consistent, which means it may or may not reflect the changes to the collection after the construction of the iterator. If this is not what you wanted, you could use the Collections.synchronizedMap() instead, which pays some penalties in concurrency.

Related