How do I set environment variables from Java? I see that I can do this for subprocesses using ProcessBuilder
. I have several subprocesses to start, though, so I'd rather modify the current process's environment and let the subprocesses inherit it.
There's a System.getenv(String)
for getting a single environment variable. I can also get a Map
of the complete set of environment variables with System.getenv()
. But, calling put()
on that Map
throws an UnsupportedOperationException
-- apparently they mean for the environment to be read only. And, there's no System.setenv()
.
So, is there any way to set environment variables in the currently running process? If so, how? If not, what's the rationale? (Is it because this is Java and therefore I shouldn't be doing evil nonportable obsolete things like touching my environment?) And if not, any good suggestions for managing the environment variable changes that I'm going to need to be feeding to several subprocesses?
For use in scenarios where you need to set specific environment values for unit tests, you might find the following hack useful. It will change the environment variables throughout the JVM (so make sure you reset any changes after your test), but will not alter your system environment.
I found that a combination of the two dirty hacks by Edward Campbell and anonymous works best, as one does not work under linux, and the other does not work under windows 7. So to get a multiplatform evil hack I combined them:
protected static void setEnv(Map<String, String> newenv) throws Exception {
try {
Class<?> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment");
Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment");
theEnvironmentField.setAccessible(true);
Map<String, String> env = (Map<String, String>) theEnvironmentField.get(null);
env.putAll(newenv);
Field theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment");
theCaseInsensitiveEnvironmentField.setAccessible(true);
Map<String, String> cienv = (Map<String, String>) theCaseInsensitiveEnvironmentField.get(null);
cienv.putAll(newenv);
} catch (NoSuchFieldException e) {
Class[] classes = Collections.class.getDeclaredClasses();
Map<String, String> env = System.getenv();
for(Class cl : classes) {
if("java.util.Collections$UnmodifiableMap".equals(cl.getName())) {
Field field = cl.getDeclaredField("m");
field.setAccessible(true);
Object obj = field.get(env);
Map<String, String> map = (Map<String, String>) obj;
map.clear();
map.putAll(newenv);
}
}
}
}
This Works like a charm. Full credits to the two authors of these hacks.
(Is it because this is Java and therefore I shouldn't be doing evil nonportable obsolete things like touching my environment?)
I think you've hit the nail on the head.
A possible way to ease the burden would be to factor out a method
void setUpEnvironment(ProcessBuilder builder) {
Map<String, String> env = builder.environment();
// blah blah
}
and pass any ProcessBuilder
s through it before starting them.
Also, you probably already know this, but you can start more than one process with the same ProcessBuilder
. So if your subprocesses are the same, you don't need to do this setup over and over.
public static void set(Map<String, String> newenv) throws Exception {
Class[] classes = Collections.class.getDeclaredClasses();
Map<String, String> env = System.getenv();
for(Class cl : classes) {
if("java.util.Collections$UnmodifiableMap".equals(cl.getName())) {
Field field = cl.getDeclaredField("m");
field.setAccessible(true);
Object obj = field.get(env);
Map<String, String> map = (Map<String, String>) obj;
map.clear();
map.putAll(newenv);
}
}
}
Or to add/update a single var and removing the loop as per thejoshwolfe's suggestion.
@SuppressWarnings({ "unchecked" })
public static void updateEnv(String name, String val) throws ReflectiveOperationException {
Map<String, String> env = System.getenv();
Field field = env.getClass().getDeclaredField("m");
field.setAccessible(true);
((Map<String, String>) field.get(env)).put(name, val);
}
Class<?> cl = env.getClass();
instead of that for loop?
Setting single environment variables (based on answer by Edward Campbell):
public static void setEnv(String key, String value) {
try {
Map<String, String> env = System.getenv();
Class<?> cl = env.getClass();
Field field = cl.getDeclaredField("m");
field.setAccessible(true);
Map<String, String> writableEnv = (Map<String, String>) field.get(env);
writableEnv.put(key, value);
} catch (Exception e) {
throw new IllegalStateException("Failed to set environment variable", e);
}
}
Usage:
First, put the method in any class you want, e.g. SystemUtil. Then call it statically:
SystemUtil.setEnv("SHELL", "/bin/bash");
If you call System.getenv("SHELL")
after this, you'll get "/bin/bash"
back.
// this is a dirty hack - but should be ok for a unittest.
private void setNewEnvironmentHack(Map<String, String> newenv) throws Exception
{
Class<?> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment");
Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment");
theEnvironmentField.setAccessible(true);
Map<String, String> env = (Map<String, String>) theEnvironmentField.get(null);
env.clear();
env.putAll(newenv);
Field theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment");
theCaseInsensitiveEnvironmentField.setAccessible(true);
Map<String, String> cienv = (Map<String, String>) theCaseInsensitiveEnvironmentField.get(null);
cienv.clear();
cienv.putAll(newenv);
}
on Android the interface is exposed via Libcore.os as a kind of hidden API.
Libcore.os.setenv("VAR", "value", bOverwrite);
Libcore.os.getenv("VAR"));
The Libcore class as well as the interface OS is public. Just the class declaration is missing and need to be shown to the linker. No need to add the classes to the application, but it also does not hurt if it is included.
package libcore.io;
public final class Libcore {
private Libcore() { }
public static Os os;
}
package libcore.io;
public interface Os {
public String getenv(String name);
public void setenv(String name, String value, boolean overwrite) throws ErrnoException;
}
throws ErrnoException
with throws Exception
.
Os.setEnv
now. developer.android.com/reference/android/system/…, java.lang.String, boolean)
This is a combination of @paul-blair 's answer converted to Java which includes some cleanups pointed out by paul blair and some mistakes that seem to have been inside @pushy 's code which is made up of @Edward Campbell and anonymous.
I cannot emphasize how much this code should ONLY be used in testing and is extremely hacky. But for cases where you need the environment setup in tests it is exactly what I needed.
This also includes some minor touches of mine that allow the code to work on both Windows running on
java version "1.8.0_92"
Java(TM) SE Runtime Environment (build 1.8.0_92-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.92-b14, mixed mode)
as well as Centos running on
openjdk version "1.8.0_91"
OpenJDK Runtime Environment (build 1.8.0_91-b14)
OpenJDK 64-Bit Server VM (build 25.91-b14, mixed mode)
The implementation:
/**
* Sets an environment variable FOR THE CURRENT RUN OF THE JVM
* Does not actually modify the system's environment variables,
* but rather only the copy of the variables that java has taken,
* and hence should only be used for testing purposes!
* @param key The Name of the variable to set
* @param value The value of the variable to set
*/
@SuppressWarnings("unchecked")
public static <K,V> void setenv(final String key, final String value) {
try {
/// we obtain the actual environment
final Class<?> processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment");
final Field theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment");
final boolean environmentAccessibility = theEnvironmentField.isAccessible();
theEnvironmentField.setAccessible(true);
final Map<K,V> env = (Map<K, V>) theEnvironmentField.get(null);
if (SystemUtils.IS_OS_WINDOWS) {
// This is all that is needed on windows running java jdk 1.8.0_92
if (value == null) {
env.remove(key);
} else {
env.put((K) key, (V) value);
}
} else {
// This is triggered to work on openjdk 1.8.0_91
// The ProcessEnvironment$Variable is the key of the map
final Class<K> variableClass = (Class<K>) Class.forName("java.lang.ProcessEnvironment$Variable");
final Method convertToVariable = variableClass.getMethod("valueOf", String.class);
final boolean conversionVariableAccessibility = convertToVariable.isAccessible();
convertToVariable.setAccessible(true);
// The ProcessEnvironment$Value is the value fo the map
final Class<V> valueClass = (Class<V>) Class.forName("java.lang.ProcessEnvironment$Value");
final Method convertToValue = valueClass.getMethod("valueOf", String.class);
final boolean conversionValueAccessibility = convertToValue.isAccessible();
convertToValue.setAccessible(true);
if (value == null) {
env.remove(convertToVariable.invoke(null, key));
} else {
// we place the new value inside the map after conversion so as to
// avoid class cast exceptions when rerunning this code
env.put((K) convertToVariable.invoke(null, key), (V) convertToValue.invoke(null, value));
// reset accessibility to what they were
convertToValue.setAccessible(conversionValueAccessibility);
convertToVariable.setAccessible(conversionVariableAccessibility);
}
}
// reset environment accessibility
theEnvironmentField.setAccessible(environmentAccessibility);
// we apply the same to the case insensitive environment
final Field theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment");
final boolean insensitiveAccessibility = theCaseInsensitiveEnvironmentField.isAccessible();
theCaseInsensitiveEnvironmentField.setAccessible(true);
// Not entirely sure if this needs to be casted to ProcessEnvironment$Variable and $Value as well
final Map<String, String> cienv = (Map<String, String>) theCaseInsensitiveEnvironmentField.get(null);
if (value == null) {
// remove if null
cienv.remove(key);
} else {
cienv.put(key, value);
}
theCaseInsensitiveEnvironmentField.setAccessible(insensitiveAccessibility);
} catch (final ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException("Failed setting environment variable <"+key+"> to <"+value+">", e);
} catch (final NoSuchFieldException e) {
// we could not find theEnvironment
final Map<String, String> env = System.getenv();
Stream.of(Collections.class.getDeclaredClasses())
// obtain the declared classes of type $UnmodifiableMap
.filter(c1 -> "java.util.Collections$UnmodifiableMap".equals(c1.getName()))
.map(c1 -> {
try {
return c1.getDeclaredField("m");
} catch (final NoSuchFieldException e1) {
throw new IllegalStateException("Failed setting environment variable <"+key+"> to <"+value+"> when locating in-class memory map of environment", e1);
}
})
.forEach(field -> {
try {
final boolean fieldAccessibility = field.isAccessible();
field.setAccessible(true);
// we obtain the environment
final Map<String, String> map = (Map<String, String>) field.get(env);
if (value == null) {
// remove if null
map.remove(key);
} else {
map.put(key, value);
}
// reset accessibility
field.setAccessible(fieldAccessibility);
} catch (final ConcurrentModificationException e1) {
// This may happen if we keep backups of the environment before calling this method
// as the map that we kept as a backup may be picked up inside this block.
// So we simply skip this attempt and continue adjusting the other maps
// To avoid this one should always keep individual keys/value backups not the entire map
LOGGER.info("Attempted to modify source map: "+field.getDeclaringClass()+"#"+field.getName(), e1);
} catch (final IllegalAccessException e1) {
throw new IllegalStateException("Failed setting environment variable <"+key+"> to <"+value+">. Unable to access field!", e1);
}
});
}
LOGGER.info("Set environment variable <"+key+"> to <"+value+">. Sanity Check: "+System.getenv(key));
}
setenv("coba","coba value");
System.out.println(System.getenv("coba"));
result: Set environment variable <coba> to <coba value>. Sanity Check: coba
value coba value
It turns out that the solution from @pushy/@anonymous/@Edward Campbell does not work on Android because Android is not really Java. Specifically, Android does not have java.lang.ProcessEnvironment
at all. But it turns out to be easier in Android, you just need to do a JNI call to POSIX setenv()
:
In C/JNI:
JNIEXPORT jint JNICALL Java_com_example_posixtest_Posix_setenv
(JNIEnv* env, jclass clazz, jstring key, jstring value, jboolean overwrite)
{
char* k = (char *) (*env)->GetStringUTFChars(env, key, NULL);
char* v = (char *) (*env)->GetStringUTFChars(env, value, NULL);
int err = setenv(k, v, overwrite);
(*env)->ReleaseStringUTFChars(env, key, k);
(*env)->ReleaseStringUTFChars(env, value, v);
return err;
}
And in Java:
public class Posix {
public static native int setenv(String key, String value, boolean overwrite);
private void runTest() {
Posix.setenv("LD_LIBRARY_PATH", "foo", true);
}
}
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);
}
Tried pushy's answer above and it worked for the most part. However, in certain circumstances, I would see this exception:
java.lang.String cannot be cast to java.lang.ProcessEnvironment$Variable
This turns out to happen when the method was called more than once, owing to the implementation of certain inner classes of ProcessEnvironment.
If the setEnv(..)
method is called more than once, when the keys are retrieved from the theEnvironment
map, they are now strings (having been put in as strings by the first invocation of setEnv(...)
) and cannot be cast to the map's generic type, Variable,
which is a private inner class of ProcessEnvironment.
A fixed version (in Scala), is below. Hopefully it isn't too difficult to carry over into Java.
def setEnv(newenv: java.util.Map[String, String]): Unit = {
try {
val processEnvironmentClass = JavaClass.forName("java.lang.ProcessEnvironment")
val theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment")
theEnvironmentField.setAccessible(true)
val variableClass = JavaClass.forName("java.lang.ProcessEnvironment$Variable")
val convertToVariable = variableClass.getMethod("valueOf", classOf[java.lang.String])
convertToVariable.setAccessible(true)
val valueClass = JavaClass.forName("java.lang.ProcessEnvironment$Value")
val convertToValue = valueClass.getMethod("valueOf", classOf[java.lang.String])
convertToValue.setAccessible(true)
val sampleVariable = convertToVariable.invoke(null, "")
val sampleValue = convertToValue.invoke(null, "")
val env = theEnvironmentField.get(null).asInstanceOf[java.util.Map[sampleVariable.type, sampleValue.type]]
newenv.foreach { case (k, v) => {
val variable = convertToVariable.invoke(null, k).asInstanceOf[sampleVariable.type]
val value = convertToValue.invoke(null, v).asInstanceOf[sampleValue.type]
env.put(variable, value)
}
}
val theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment")
theCaseInsensitiveEnvironmentField.setAccessible(true)
val cienv = theCaseInsensitiveEnvironmentField.get(null).asInstanceOf[java.util.Map[String, String]]
cienv.putAll(newenv);
}
catch {
case e : NoSuchFieldException => {
try {
val classes = classOf[java.util.Collections].getDeclaredClasses
val env = System.getenv()
classes foreach (cl => {
if("java.util.Collections$UnmodifiableMap" == cl.getName) {
val field = cl.getDeclaredField("m")
field.setAccessible(true)
val map = field.get(env).asInstanceOf[java.util.Map[String, String]]
// map.clear() // Not sure why this was in the code. It means we need to set all required environment variables.
map.putAll(newenv)
}
})
} catch {
case e2: Exception => e2.printStackTrace()
}
}
case e1: Exception => e1.printStackTrace()
}
}
import java.lang.{Class => JavaClass}
.
Poking around online, it looks like it might be possible to do this with JNI. You'd then have to make a call to putenv() from C, and you'd (presumably) have to do it in a way that worked on both Windows and UNIX.
If all that can be done, it surely wouldn't be too hard for Java itself to support this instead of putting me in a straight jacket.
A Perl-speaking friend elsewhere suggests that this is because environment variables are process global and Java is striving for good isolation for good design.
LD_LIBRARY_PATH
before calling Runtime.loadLibrary()
; the dlopen()
call it invokes looks at the real environment, not at Java's idea of same).
There are a three libraries out there that can do this during unit tests.
There's Stefan Birkner's System Rules and System Lambda - https://www.baeldung.com/java-system-rules-junit which allow you to do something like:
public class JUnitTest {
@Rule
public EnvironmentVariables environmentVariables = new EnvironmentVariables();
@Test
public void someTest() {
environmentVariables.set("SOME_VARIABLE", "myValue");
// now System.getenv does what you want
}
}
or for System-Lambda:
@Test
void execute_code_with_environment_variables(
) throws Exception {
List<String> values = withEnvironmentVariable("first", "first value")
.and("second", "second value")
.execute(() -> asList(
System.getenv("first"),
System.getenv("second")
));
assertEquals(
asList("first value", "second value"),
values
);
}
The above features are also available as JUnit 5 extension via System Stubs:
@ExtendWith(SystemStubsExtension.class)
class SomeTest {
@SystemStub
private EnvironmentVariables;
@Test
void theTest() {
environmentVariables.set("SOME_VARIABLE", "myValue");
// now System.getenv does what you want
}
}
System Stubs is backwards compatible with System Lambda and System Rules, but supports JUnit 5.
Alternatively, there's also JUnit Pioneer - https://github.com/junit-pioneer/junit-pioneer, which allows for environment variables to be set at test time via annotations.
@SetEnvironmentVariable(key = "SOME_VARIABLE", value = "myValue")
on the test method and you're good to go.
Tim Ryan's answer worked for me... but I wanted it for Groovy (Spock context for example), and simplissimo:
import java.lang.reflect.Field
def getModifiableEnvironmentMap() {
def unmodifiableEnv = System.getenv()
Class cl = unmodifiableEnv.getClass()
Field field = cl.getDeclaredField("m")
field.accessible = true
field.get(unmodifiableEnv)
}
def clearEnvironmentVars( def keys ) {
def savedVals = [:]
keys.each{ key ->
String val = modifiableEnvironmentMap.remove(key)
// thinking about it, I'm not sure why we need this test for null
// but haven't yet done any experiments
if( val != null ) {
savedVals.put( key, val )
}
}
savedVals
}
def setEnvironmentVars(Map varMap) {
modifiableEnvironmentMap.putAll(varMap)
}
// pretend existing Env Var doesn't exist
def PATHVal1 = System.env.PATH
println "PATH val1 |$PATHVal1|"
String[] keys = ["PATH", "key2", "key3"]
def savedVars = clearEnvironmentVars(keys)
def PATHVal2 = System.env.PATH
println "PATH val2 |$PATHVal2|"
// return to reality
setEnvironmentVars(savedVars)
def PATHVal3 = System.env.PATH
println "PATH val3 |$PATHVal3|"
println "System.env |$System.env|"
// pretend a non-existent Env Var exists
setEnvironmentVars( [ 'key4' : 'key4Val' ])
println "key4 val |$System.env.key4|"
Setting the environment variables in the current Java process which uses native code (a dll) which relies on the environment variable in question, only works when you set this environment variable natively.
Most of the examples here you are changing the maps in the JVM, but will not work native.
One way I saw is through JNI, which probably works as well. Another way is using JNA Platform by using the Kernel32 interface (windows only) For example:
private static void setEnv(String key, String value) {
if(isWindows()) {
if (!Kernel32.INSTANCE.SetEnvironmentVariable(key, value)) {
System.err.println("Unable to set the environemnt variable: " + key);
}
}
}
For a unix like OS, the LibCAPI interface can be used, but have not tried that one.
if( isMac() || isLinux() ) { if( LibC.INSTANCE.setenv( key, value, 1 ) != 0 ) { System.err.println( "Unable to set the environemnt variable: " + key ); } }
does the trick on linux and macos.
If you are facing this problem with testing like me, and you are using Junit5, Junit-pioneer comes with very helpful annotations. maven release
example:
@Test
@SetEnvironmentVariable(key = "some variable",value = "new value")
void test() {
assertThat(System.getenv("some variable")).
isEqualTo("new value");
}
can not recommend it enough.
This is the Kotlin evil version of the @pushy's evil answer =)
@Suppress("UNCHECKED_CAST")
@Throws(Exception::class)
fun setEnv(newenv: Map<String, String>) {
try {
val processEnvironmentClass = Class.forName("java.lang.ProcessEnvironment")
val theEnvironmentField = processEnvironmentClass.getDeclaredField("theEnvironment")
theEnvironmentField.isAccessible = true
val env = theEnvironmentField.get(null) as MutableMap<String, String>
env.putAll(newenv)
val theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment")
theCaseInsensitiveEnvironmentField.isAccessible = true
val cienv = theCaseInsensitiveEnvironmentField.get(null) as MutableMap<String, String>
cienv.putAll(newenv)
} catch (e: NoSuchFieldException) {
val classes = Collections::class.java.getDeclaredClasses()
val env = System.getenv()
for (cl in classes) {
if ("java.util.Collections\$UnmodifiableMap" == cl.getName()) {
val field = cl.getDeclaredField("m")
field.setAccessible(true)
val obj = field.get(env)
val map = obj as MutableMap<String, String>
map.clear()
map.putAll(newenv)
}
}
}
It is working in macOS Mojave at least.
I stumbled upon this thread as I had a similar requirement where I needed to set (or update) an Environment Variable (permanently).
So I looked in to - How to set an environment variable permanently from Command Prompt and it was very simple!
setx JAVA_LOC C:/Java/JDK
Then I just implemented the same in my code Here's what I used (suppose - JAVA_LOC is the env. variable name)
String cmdCommand = "setx JAVA_LOC " + "C:/Java/JDK";
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command("cmd.exe", "/c", cmdCommand);
processBuilder.start();
ProcessBuilder fires-up a cmd.exe and passes the command that you want. The environment variable is retained even if you kill JVM/reboot the system as it has no relation with the JVM/Program's lifecycle.
just use reflect, like setEnv("k1","v1")
private void setEnv(String key, String val) throws Exception {
getModifiableEnv().put(key, val);
}
private Map<String, String> getModifiableEnv() throws Exception {
Map<String, String> unmodifiableEnv = System.getenv();
Field field = unmodifiableEnv.getClass().getDeclaredField("m");
field.setAccessible(true);
return (Map<String, String>) field.get(unmodifiableEnv);
}
need
import java.lang.reflect.Field;
import java.util.Map;
jython variant based on @pushy's answer, works on windows.
def set_env(newenv):
from java.lang import Class
process_environment = Class.forName("java.lang.ProcessEnvironment")
environment_field = process_environment.getDeclaredField("theEnvironment")
environment_field.setAccessible(True)
env = environment_field.get(None)
env.putAll(newenv)
invariant_environment_field = process_environment.getDeclaredField("theCaseInsensitiveEnvironment");
invariant_environment_field.setAccessible(True)
invevn = invariant_environment_field.get(None)
invevn.putAll(newenv)
Usage:
old_environ = dict(os.environ)
old_environ['EPM_ORACLE_HOME'] = r"E:\Oracle\Middleware\EPMSystem11R1"
set_env(old_environ)
A version in Kotlin, in this algorithm I created a decorator that allows you to set and get variables from the environment.
import java.util.Collections
import kotlin.reflect.KProperty
class EnvironmentDelegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return System.getenv(property.name) ?: "-"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
val key = property.name
val classes: Array<Class<*>> = Collections::class.java.declaredClasses
val env = System.getenv()
val cl = classes.first { "java.util.Collections\$UnmodifiableMap" == it.name }
val field = cl.getDeclaredField("m")
field.isAccessible = true
val obj = field[env]
val map = obj as MutableMap<String, String>
map.putAll(mapOf(key to value))
}
}
class KnownProperties {
var JAVA_HOME: String by EnvironmentDelegate()
var sample: String by EnvironmentDelegate()
}
fun main() {
val knowProps = KnownProperties()
knowProps.sample = "2"
println("Java Home: ${knowProps.JAVA_HOME}")
println("Sample: ${knowProps.sample}")
}
Kotlin implementation I recently made based on Edward's answer:
fun setEnv(newEnv: Map<String, String>) {
val unmodifiableMapClass = Collections.unmodifiableMap<Any, Any>(mapOf()).javaClass
with(unmodifiableMapClass.getDeclaredField("m")) {
isAccessible = true
@Suppress("UNCHECKED_CAST")
get(System.getenv()) as MutableMap<String, String>
}.apply {
clear()
putAll(newEnv)
}
}
If you work with SpringBoot you can add specifying the environmental variable in the following property:
was.app.config.properties.toSystemProperties
You can pass parameters into your initial java process with -D:
java -cp <classpath> -Dkey1=value -Dkey2=value ...
System.getProperty
and are not the same as System.getenv
. Besides, the System
class also allows to set these properties statically using setProperty
Success story sharing
import java.lang.reflect.Field;