Search code examples
javaenvironment-variablesprocessbuilder

Setting the environment for ProcessBuilder


I have a strange problem setting the Linux environment from Java (1.6); specifically the "PATH" variable.

In a nutshell, I have a pipeline for running native processes, which uses java.lang.ProcessBuilder. The user can optionally set the environment variables via a HashMap named environment:

ProcessBuilder pb = new ProcessBuilder(args);
Map<String, String> env = pb.environment();
if (environment != null)
   env.putAll(environment);
Process process = pb.start();

The env variable gets set properly, if I dump it to the console, with a correct value for the PATH variable. However, running the process results in a thrown Exception:

java.io.IOException: error=2, No such file or directory

The same process runs fine with identical environment variables in the terminal shell. To test this, I ran Eclipse AFTER setting the environment in the terminal. In this case the ProcessBuilder process runs correctly.

So what must be happening is that the ProcessBuilder is not using the environment I set for it, but the current System environment instead.

I can't find any satisfactory answers to this problem online. Perhaps this is an OS-specific issue? Or something else I'm missing?


Solution

  • You need to understand that environment variables are local to process contexts. A new process gets a copy of the parent's environment but each copy is independent. Changes in the parent don't affect existing children (only new ones) and changes in children don't affect the parent or new children of the parent.

    In your case, the Java process creates child process and puts a modified PATH variable into the child's context. This doesn't affect the Java process. The child process isn't a shell, so it ignores the PATH variable. The process is created directly using OS services. Those look into the context of the Java process which contains the old PATH variable unless you change the environment in the shell before you start the Java process.

    To fix your issue, you have two choices:

    1. Examine the PATH variable in Java, split it into path elements and search for the executable manually. You can then invoke ProcessBuilder with the absolute path and put the new PATH into the child, so grandchildren will have the correct path.

    2. Invoke a shell to start the child process. The shell will use it's path (which you can pass via the environment).

    The second case works like this:

    1. You create an environment with the correct PATH.
    2. You start a shell process.
    3. You pass the command to run as argument to the shell ("sh", "-c", "cmd args" or "cmd.exe", "/c", "cmd args")
    4. The shell will notice that it has to run a command
    5. It will look into it's environment (which you configured in step #1), find the modified PATH and run the correct command.

    The drawback of the second case is that you have to properly escape and/or quote the arguments for the command (args), or spaces and other special characters will cause problems.