2

Why am I experiencing different behavior in these 2 cases? Have I missed something out?

Command:

new ProcessBuilder().directory(
                    Paths.get(System.getProperty("user.dir")).toFile())
                    .command("/usr/bin/java -Djava.library.path=/Users/myusername/myproject/lib/DynamoDBLocal_lib/ -jar /Users/myusername/myproject/lib/DynamoDBLocal.jar  -sharedDb").start();

Stack Trace:

Exception in thread "main" java.lang.RuntimeException: java.io.IOException: Cannot run program "/usr/bin/java -Djava.library.path=/Users/myusername/myproject/lib/DynamoDBLocal_lib/ -jar /Users/myusername/myproject/lib/DynamoDBLocal.jar  -sharedDb" (in directory "/Users/myusername/myproject"): error=2, No such file or directory
    at com.comcast.tvx.app.xreserver.Main.exec(Main.java:47)
    at com.comcast.tvx.app.xreserver.Main.main(Main.java:16)
Caused by: java.io.IOException: Cannot run program "/usr/bin/java -Djava.library.path=/Users/myusername/myproject/lib/DynamoDBLocal_lib/ -jar /Users/myusername/myproject/lib/DynamoDBLocal.jar  -sharedDb" (in directory "/Users/myusername/myproject"): error=2, No such file or directory
    at java.lang.ProcessBuilder.start(ProcessBuilder.java:1048)
    at com.comcast.tvx.app.xreserver.Main.exec(Main.java:44)
    ... 1 more
Caused by: java.io.IOException: error=2, No such file or directory
    at java.lang.UNIXProcess.forkAndExec(Native Method)
    at java.lang.UNIXProcess.<init>(UNIXProcess.java:248)
    at java.lang.ProcessImpl.start(ProcessImpl.java:134)
    at java.lang.ProcessBuilder.start(ProcessBuilder.java:1029)
    ... 2 more

Environment in failing case:

{PATH=/usr/bin:/bin:/usr/sbin:/sbin
JAVA_STARTED_ON_FIRST_THREAD_1074=1
SHELL=/bin/zsh
SECURITYSESSIONID=186a4
USER=myusername
JAVA_MAIN_CLASS_25188=com.comcast.tvx.app.xreserver.Main
APP_ICON_1074=../Resources/Eclipse.icns
COMMAND_MODE=unix2003
TMPDIR=/var/folders/t_/dlj2wfdj0bx2xl6mnnqmxyhj99pf4b/T/
SSH_AUTH_SOCK=/tmp/launch-Bhd1It/Listeners
DISPLAY=/tmp/launch-PuSx66/org.macosforge.xquartz:0
__CF_USER_TEXT_ENCODING=0x529B388B:0:0
Apple_PubSub_Socket_Render=/tmp/launch-hB7zpQ/Render
__CHECKFIX1436934=1
LOGNAME=myusername
HOME=/Users/myusername}

More information

When I do it with Runtime.exec() the environment looks identical but I don't get the error:

Command:

Runtime.getRuntime().exec("/usr/bin/java -Djava.library.path=/Users/myusername/myproject/lib/DynamoDBLocal_lib/ -jar /Users/myusername/myproject/lib/DynamoDBLocal.jar  -sharedDb")

Environment in succeeding case:

{PATH=/usr/bin:/bin:/usr/sbin:/sbin
JAVA_STARTED_ON_FIRST_THREAD_1074=1
SHELL=/bin/zsh
JAVA_MAIN_CLASS_25360=com.comcast.tvx.app.xreserver.Main
SECURITYSESSIONID=186a4
USER=myusername
APP_ICON_1074=../Resources/Eclipse.icns
COMMAND_MODE=unix2003
TMPDIR=/var/folders/t_/dlj2wfdj0bx2xl6mnnqmxyhj99pf4b/T/
SSH_AUTH_SOCK=/tmp/launch-Bhd1It/Listeners
DISPLAY=/tmp/launch-PuSx66/org.macosforge.xquartz:0
__CF_USER_TEXT_ENCODING=0x529B388B:0:0
Apple_PubSub_Socket_Render=/tmp/launch-hB7zpQ/Render
__CHECKFIX1436934=1
LOGNAME=myusername
HOME=/Users/myusername}

1 Answer 1

9

Try to change this line:

.command("/usr/bin/java -Djava.library.path=/Users/myusername/myproject/lib/DynamoDBLocal_lib/ -jar /Users/myusername/myproject/lib/DynamoDBLocal.jar  -sharedDb").start();

to (scroll all the way to the right to see the difference):

.command("/usr/bin/java -Djava.library.path=/Users/myusername/myproject/lib/DynamoDBLocal_lib/ -jar /Users/myusername/myproject/lib/DynamoDBLocal.jar  -sharedDb".split("\\s+")).start();

Explanation: the input to command() should be an array (or list) of strings each of which is another argument (or "token"). It should not contain whitespaces!

From the docs:

a command, a list of strings which signifies the external program file to be invoked and its arguments, if any. Which string lists represent a valid operating system command is system-dependent. For example, it is common for each conceptual argument to be an element in this list, but there are operating systems where programs are expected to tokenize command line strings themselves - on such a system a Java implementation might require commands to contain exactly two elements.

Sign up to request clarification or add additional context in comments.

2 Comments

In case anyone is wondering why it is designed this way, including my precious self, it frees you from worrying about escaping especial characters. Sadly, when you look at the signature but not the javadoc you think it means something else.
On UNIX / Linux based systems, running a child process involves passing a command name, and array of command arguments and an array of environment variables as arguments to an exec (or similar) system call. ProcessBuilder is doing the same thing. The alternative would be to try to emulate behavior like quoting, escaping and so on that are normally implemented by a shell. Or more precisely, by many different shells in different ways. That alternative is untenable ... and that's the real reason ProcessBuilder (and Process.exec(...)) were designed this way.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.