Proper handling of the ProcessBuilder

This post is outdated! A newer Version can bei found here: How to handle the output of the ProcessBuilder!

This article describes the proper handling of the streams which are provided by the ProccessBuilder. If you are interested in the common usage and the difference between Runtime and Processbuilder, refere from Runtime.exec to ProcessBuilder.

I had good experiences with the ProcessBuilder to call native commands. That’s why I would like to use it for another project. But testing it in the new project environment, it often hung.

But why and what’s the difference to the other project? It was the quantity of output.

In the JDK documentation of the ProcessBuilder I’ve found right hint:

The parent process uses these streams (#getInputStream(), #getErrorStream()) to feed input to and get output from the subprocess. Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, and even deadlock.

That means: The streams must be read!!! Depending on the native platform, the quantity of output (info or error, what ever) can cause a buffer overflow and therefore an hanging Process.

The code above shows is a wrapper to the ProcessBuilder which boozes the streams:

/**
 * LGPL
 */
public class ProcessBuilderWrapper {
	private StringWriter infos;
	private StringWriter errors;
	private int status;

	public ProcessBuilderWrapper(File directory, List command) throws Exception {
		infos = new StringWriter();
		errors = new StringWriter();
		ProcessBuilder pb = new ProcessBuilder(command);
		if(directory != null)
			pb.directory(directory);
		Process process = pb.start();
		StreamBoozer seInfo = new StreamBoozer(process.getInputStream(), new PrintWriter(infos, true));
		StreamBoozer seError = new StreamBoozer(process.getErrorStream(), new PrintWriter(errors, true));
		seInfo.start();
		seError.start();
		status = process.waitFor();
		seInfo.join();
		seError.join();
	}

	public ProcessBuilderWrapper(List command) throws Exception {
		this(null, command);
	}

	public String getErrors() {
		return errors.toString();
	}

	public String getInfos() {
		return infos.toString();
	}

	public int getStatus() {
		return status;
	}

	class StreamBoozer extends Thread {
		private InputStream in;
		private PrintWriter pw;

		StreamBoozer(InputStream in, PrintWriter pw) {
			this.in = in;
			this.pw = pw;
		}

		@Override
		public void run() {
			BufferedReader br = null;
			try {
				br = new BufferedReader(new InputStreamReader(in));
				String line = null;
	            while ( (line = br.readLine()) != null) {
	            	pw.println(line);
	            }
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				try {
					br.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

Example:

List cmd = new ArrayList();
cmd.add("ls");
cmd.add("-al");
ProcessBuilderWrapper pbd = new ProcessBuilderWrapper(new File("/tmp"), cmd);
System.out.println("Command has terminated with status: " + pbd.getStatus());
System.out.println("Output:\n" + pbd.getInfos());
System.out.println("Error: " + pbd.getErrors());

, ,

  1. #1 by gnom1gnom on 2012/11/22 - 17:04

    Thank you, this solved my problem!

  2. #2 by Pablito on 2013/01/04 - 19:38

    you are not calling the run method. I do not see any output on my screen

    • #3 by Thilo on 2013/01/04 - 20:53

      What do you mean with “you are not calling the run method”?

      In general: You have to start a Thread by calling Thread#start. See http://docs.oracle.com/javase/6/docs/api/java/lang/Thread.html.

      What OS are you using? What’s your command?

      I’m using this peace of code for Linux, OS X and different Windows systems successful.

  3. #4 by Pablito on 2013/01/04 - 21:51

    i am using solaris 10. I am trying to execute a command that launches a cshell script and i do not see any output? Nothing happens and i am wondering where do you drain the input and error stream? Another thing is you have two constructors. One that takes in a directory and another that only takes in a command. My question is do you need to specify the directory or just have one large strings that contains the path and the file to execute. I used to use the runtime.exec which only needed the path and file to execute but it seems this one is not working

  4. #5 by Pablito on 2013/01/04 - 22:01

    this sample code does not work if you want to execute a shell script. Do you need to specify the path. I even tried to execute a simple ls command it it does not show anything

    • #6 by Thilo on 2013/01/05 - 10:19

      The directory is the working directory of your command. You don’t need it in every context, that’s why I have two constructors.
      If you are to call a scripts you have to specify the full path in your command.
      Your Script doesn’t have all environment variables and paths like on command line.
      You have to split your complete command by spaces. That’s your List ‘command’.

      Hope that helps!

  5. #7 by Pablito on 2013/01/10 - 00:24

    hey how do i execute the following command withthe process builder. I am using solaris and i am getting an error saying bad subsititution. cd bin; pwd

    • #8 by Thilo on 2013/01/10 - 19:39

      the full path to the ‘bin’ folder is your working directory and the full path to ‘pwd’ your command

  6. #9 by Gede on 2013/11/13 - 08:38

    Thank you for your tutorial, this is great.
    it really help me.

  7. #10 by Ban Ăn Chơi on 2014/02/11 - 09:12

    Thanks, nice post.

  8. #11 by David on 2014/02/20 - 06:14

    You may want to add the lines below

    seInfo.join();
    seError.join();

    process thread gets done before the other threads…

    • #12 by Thilo on 2014/02/20 - 07:47

      Yes, you are right!
      Thanks for your hint.

  9. #13 by Akshay Kapoor on 2014/08/07 - 08:41

    Does this handle the runtime inputs required while executing a command on the system. For eg. a command is being executed which asks at runtime If i want to proceed further.

    • #14 by Thilo on 2014/08/07 - 17:57

      My code example definitely not! And I think the ProcessBuilder hasn’t the ability to do that.

  10. #15 by John on 2015/01/07 - 04:04

    I am sorry this might be a stupid question but I still want to ask! I understand there will be problem if the size of output is larger than the standard buffer size, can you please tell me how to avoid the issue if the output size is really large? I tried to understand your sample code but still couldn’t figure out how.

    • #16 by Thilo on 2015/01/07 - 20:36

      I guess, you’ll run into an IOException (buffer overlow).
      The default buffer size is 8,192 bytes. This should be large enough for the most purposes!

      If not, the default size must be specified for the BufferedReader.

  11. #17 by xuanchien on 2017/02/20 - 09:50

    In my sub-program (which is a JAR file), it also has a loop to wait for user input and process it. Its code is like this:

    while ((line = reader.readLine()) != null){ … }

    The only way to break this loop is to press Ctrl + C. Because of this, when I invoke this JAR from my main program using ProcessBuilder, it hangs when I try to call br.readLine() (perhaps because JAR file is also calling readLine() at that time?)

    Do you have any ideas how to solve this issue?

    Best regards,

    • #18 by Thilo on 2017/02/20 - 13:11

      Hi xuanchaun,
      I don’t know enough about your code. But I think the problem is the reading from the system’s InputStream in two different ‘jobs’. May be a solution could be to run each jobs in separate threads. But I’m not sure!

      I hope that helps you

Leave a reply to gnom1gnom Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.