Java Process Example

Startup a native process from Java using Runtime. Read the output from the process and write some data to it. After you are done, terminate the process.

1. Introduction

Have you ever run into a situation where you need to execute an OS command from inside java and read its output? You can use the Process class to do so, but there are some caveats to consider.

2. Creating a Process

First of all, the simplest way to run an OS command in Java is to use Runtime.exec(). The method returns a Process object which can then be used to read its output, among other things.

The following example runs env in a process and reads the output written by the process and shows it on the console. It then waits for the process to exit using waitFor().

Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec("env");
try (InputStream in = process.getInputStream();) {
    byte[] bytes = new byte[2048];
    int len;
    while ((len = in.read(bytes)) != -1) {
	System.out.write(bytes, 0, len);
    }
}
System.out.println("Process exited with: " + process.waitFor());

// prints a whole bunch of environment variables
...
SSH_AGENT_PID=4189
SHELL=/bin/bash
...
Process exited with: 0

3. Specify Process Environment for Security

As you can see, when invoked as above the process receives all the environment variables of the invoking process. In general, that is not such a good idea because it can reveal a whole lot about the invoking process. Especially if you follow the 12-factor app guidelines, some of your passwords might be visible in the environment! These would then be available to the invoked process. To avoid this, follow a rule of “need-to-know”. In other words, pass only the variables that the invoked command needs to do its job.

In general, you should prefer the following invocation in preference to the above.

Process process = runtime.exec("env", new String[0]);

If you need to pass some explicit values for environment variables, use the following:

String[] env = Arrays
    .asList("hello=world",
	    "message=how are we today?")
    .toArray(new String[1]);
Process process = runtime.exec("env", env);

// prints:
message=how are we today?
hello=world
Process exited with: 0

Environment variables are passed to the process without any interpretation whatsoever. In other words, shell variables (on Unix/Linux) are not processed in any special way.

4. Read Lines from Process

Use the Process.getInputStream() to set up a Reader to read from the target process. For example, the following reads and echoes lines with a line number from the child process.

Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec("cat sample.txt", new String[0]);
try (BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));) {
    String line;
    int n = 0;
    while ((line = in.readLine()) != null) {
	n++;
	System.out.println(n + ") " + line);
    }
}

// sample.txt
1985,ATL,NL,harpete01,250000
1985,ATL,NL,hornebo01,1500000
1985,ATL,NL,hubbagl01,455000

// prints with the line number:
1) 1985,ATL,NL,harpete01,250000
2) 1985,ATL,NL,hornebo01,1500000
3) 1985,ATL,NL,hubbagl01,455000

5. Writing Data to Process

In addition to being to read from a process, you can use the Process.getOutputStream() to write data (text or binary) to the process. The next example writes some binary data to the process which computes a SHA256 sum and writes the output. Subsequently the program reads the output and prints it.

Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec("sha256sum", new String[0]);
try (OutputStream out = process.getOutputStream();
     InputStream fin = new FileInputStream("sample.png");) {
    byte[] bytes = new byte[2048];
    int len;
    while ((len = fin.read(bytes)) != -1) {
	out.write(bytes, 0, len);
    }
}

try (InputStream in = process.getInputStream();) {
    byte[] bytes = new byte[2048];
    int len;
    while ((len = in.read(bytes)) != -1) {
	System.out.write(bytes, 0, len);
    }
}

// prints:
dac592632f7f2e4a4650f0593a4186691543faa931fc57df4647861567f272e2 -

While the above code writes and reads from the child in a single thread, this is generally not a good idea. It works for this simple case but for more complex scenarios, you need to use separate threads for the read and write blocks. We discuss this in the next part of this guide.

6. Terminating a Process

Starting up a child process is all fine and dandy, now how about terminating it? You can use Process.destroy() to request termination nicely and Process.destroyForcibly() to kill a child process.

In the following example code, a child process is started with “cat” which just waits for the parent process to write something. However, the code below does not write anything and just attempts to read data. Which is not going to happen. So we have a separate thread which waits for 5 secs and attempts to destroy the child. And if that does not work, it kills the child process. Adios, son!

Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec("cat", new String[0]);
ExecutorService esvc = Executors.newCachedThreadPool();
Runnable reader = () -> {
    try (InputStream in = process.getInputStream();) {
        byte[] bytes = new byte[2048];
        int len;
        while ((len = in.read(bytes)) != -1) {
            System.out.write(bytes, 0, len);
        }
    } catch(java.io.IOException ex) {
        System.err.println(ex.getMessage());
    }
};
esvc.submit(reader);
        
Runnable waiter = () -> {
    try {
        System.out.println("Waiting for process exit ..");
        if ( process.waitFor(5, TimeUnit.SECONDS) ) {
            System.out.println("Process exited with: " +
                               process.exitValue());
        } else {
            System.out.println("Process has not exited .. destroying");
            process.destroy();
            if ( process.waitFor(5, TimeUnit.SECONDS) ) {
                System.out.println("Destruction .. exit code: " +
                                   process.exitValue());
            } else {
                System.out.println("Attempting forcible destruction ..");
                process.destroyForcibly();
                if ( process.waitFor(5, TimeUnit.SECONDS) )
                    System.out.println("Process forcibly destroyed .. exit code: " + process.exitValue());
            }
        }
    } catch(InterruptedException ex) {}
};
esvc.submit(waiter).get();
esvc.shutdown();

Summary

Runtime can be used to create a native OS process from inside java. Input and output streams are provided for reading its output and writing data to it. Its environment should probably be explicitly set to avoid passing all parent environment variables. The process can be terminated at any time by calling destroy().

Leave a Reply

Your email address will not be published. Required fields are marked *