ProcessBuilder: Forwarding stdout and stderr of started processes without blocking the main thread
Use ProcessBuilder.inheritIO
, it sets the source and destination for subprocess standard I/O to be the same as those of the current Java process.
Process p = new ProcessBuilder().inheritIO().command("command1").start();
If Java 7 is not an option
public static void main(String[] args) throws Exception {
Process p = Runtime.getRuntime().exec("cmd /c dir");
inheritIO(p.getInputStream(), System.out);
inheritIO(p.getErrorStream(), System.err);
}
private static void inheritIO(final InputStream src, final PrintStream dest) {
new Thread(new Runnable() {
public void run() {
Scanner sc = new Scanner(src);
while (sc.hasNextLine()) {
dest.println(sc.nextLine());
}
}
}).start();
}
Threads will die automatically when subprocess finishes, because src
will EOF.
To only way in Java 6 or earlier is with a so called StreamGobbler
(which you are started to create):
StreamGobbler errorGobbler = new StreamGobbler(p.getErrorStream(), "ERROR");
// any output?
StreamGobbler outputGobbler = new StreamGobbler(p.getInputStream(), "OUTPUT");
// start gobblers
outputGobbler.start();
errorGobbler.start();
...
private class StreamGobbler extends Thread {
InputStream is;
String type;
private StreamGobbler(InputStream is, String type) {
this.is = is;
this.type = type;
}
@Override
public void run() {
try {
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null)
System.out.println(type + "> " + line);
}
catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
For Java 7, see Evgeniy Dorofeev's answer.
A flexible solution with Java 8 lambda that lets you provide a Consumer
that will process the output (eg. log it) line by line. run()
is a one-liner with no checked exceptions thrown. Alternatively to implementing Runnable
, it can extend Thread
instead as other answers suggest.
class StreamGobbler implements Runnable {
private InputStream inputStream;
private Consumer<String> consumeInputLine;
public StreamGobbler(InputStream inputStream, Consumer<String> consumeInputLine) {
this.inputStream = inputStream;
this.consumeInputLine = consumeInputLine;
}
public void run() {
new BufferedReader(new InputStreamReader(inputStream)).lines().forEach(consumeInputLine);
}
}
You can then use it for example like this:
public void runProcessWithGobblers() throws IOException, InterruptedException {
Process p = new ProcessBuilder("...").start();
Logger logger = LoggerFactory.getLogger(getClass());
StreamGobbler outputGobbler = new StreamGobbler(p.getInputStream(), System.out::println);
StreamGobbler errorGobbler = new StreamGobbler(p.getErrorStream(), logger::error);
new Thread(outputGobbler).start();
new Thread(errorGobbler).start();
p.waitFor();
}
Here the output stream is redirected to System.out
and the error stream is logged on the error level by the logger
.