Like most people who have found this thread, I was writing some unit tests and needed to modify the environment variables to set the correct conditions for the test to run. However, I found the most upvoted answers had some issues and/or were very cryptic or overly complicated. Hopefully this will help others to sort out the solution more quickly.
First off, I finally found @Hubert Grzeskowiak's solution to be the simplest and it worked for me. I wish I would have come to that one first. It's based on @Edward Campbell's answer, but without the complicating for loop search.
However, I started with @pushy's solution, which got the most upvotes. It is a combo of @anonymous and @Edward Campbell's. @pushy claims both approaches are needed to cover both Linux and Windows environments. I'm running under OS X and find that both work (once an issue with @anonymous approach is fixed). As others have noted, this solution works most of the time, but not all.
I think the source of most of the confusion comes from @anonymous's solution operating on the 'theEnvironment' field. Looking at the definition of the ProcessEnvironment structure, 'theEnvironment' is not a Map< String, String > but rather it is a Map< Variable, Value >. Clearing the map works fine, but the putAll operation rebuilds the map a Map< String, String >, which potentially causes problems when subsequent operations operate on the data structure using the normal API that expects Map< Variable, Value >. Also, accessing/removing individual elements is a problem. The solution is to access 'theEnvironment' indirectly through 'theUnmodifiableEnvironment'. But since this is a type UnmodifiableMap the access must be done through the private variable 'm' of the UnmodifiableMap type. See getModifiableEnvironmentMap2 in code below.
In my case I needed to remove some of the environment variables for my test (the others should be unchanged). Then I wanted to restore the environment variables to their prior state after the test. The routines below make that straight forward to do. I tested both versions of getModifiableEnvironmentMap on OS X, and both work equivalently. Though based on comments in this thread, one may be a better choice than the other depending on the environment.
Note: I did not include access to the 'theCaseInsensitiveEnvironmentField' since that seems to be Windows specific and I had no way to test it, but adding it should be straight forward.
private Map<String, String> getModifiableEnvironmentMap() {
try {
Map<String,String> unmodifiableEnv = System.getenv();
Class<?> cl = unmodifiableEnv.getClass();
Field field = cl.getDeclaredField("m");
field.setAccessible(true);
Map<String,String> modifiableEnv = (Map<String,String>) field.get(unmodifiableEnv);
return modifiableEnv;
} catch(Exception e) {
throw new RuntimeException("Unable to access writable environment variable map.");
}
}
private Map<String, String> getModifiableEnvironmentMap2() {
try {
Class<?> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment");
Field theUnmodifiableEnvironmentField = processEnvironmentClass.getDeclaredField("theUnmodifiableEnvironment");
theUnmodifiableEnvironmentField.setAccessible(true);
Map<String,String> theUnmodifiableEnvironment = (Map<String,String>)theUnmodifiableEnvironmentField.get(null);
Class<?> theUnmodifiableEnvironmentClass = theUnmodifiableEnvironment.getClass();
Field theModifiableEnvField = theUnmodifiableEnvironmentClass.getDeclaredField("m");
theModifiableEnvField.setAccessible(true);
Map<String,String> modifiableEnv = (Map<String,String>) theModifiableEnvField.get(theUnmodifiableEnvironment);
return modifiableEnv;
} catch(Exception e) {
throw new RuntimeException("Unable to access writable environment variable map.");
}
}
private Map<String, String> clearEnvironmentVars(String[] keys) {
Map<String,String> modifiableEnv = getModifiableEnvironmentMap();
HashMap<String, String> savedVals = new HashMap<String, String>();
for(String k : keys) {
String val = modifiableEnv.remove(k);
if (val != null) { savedVals.put(k, val); }
}
return savedVals;
}
private void setEnvironmentVars(Map<String, String> varMap) {
getModifiableEnvironmentMap().putAll(varMap);
}
@Test
public void myTest() {
String[] keys = { "key1", "key2", "key3" };
Map<String, String> savedVars = clearEnvironmentVars(keys);
// do test
setEnvironmentVars(savedVars);
}