Read from the InputStream. You can append the output to a StringBuilder:

BufferedReader reader = 
                new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuilder builder = new StringBuilder();
String line = null;
while ( (line = reader.readLine()) != null) {
   builder.append(line);
   builder.append(System.getProperty("line.separator"));
}
String result = builder.toString();
Answer from Reimeus on Stack Overflow
🌐
Oracle
docs.oracle.com › javase › 8 › docs › api › java › lang › ProcessBuilder.html
ProcessBuilder (Java Platform SE 8 )
April 21, 2026 - If the destination is set to any other value, then Process.getInputStream() will return a null input stream. ... IllegalArgumentException - if the redirect does not correspond to a valid destination of data, that is, has type READ ... Sets this process builder's standard error destination. Subprocesses subsequently started by this object's start() method send their standard error to this destination. If the destination is Redirect.PIPE (the initial value), then the error output of a subprocess can be read using the input stream returned by Process.getErrorStream().
🌐
Leo3418
leo3418.github.io › 2021 › 06 › 20 › java-processbuilder-stdout.html
Properly Handling Process Output When Using Java’s ProcessBuilder - Leo3418's Personal Site
Although this could solve the issue, it would significantly alter the behavior of java-ebuilder because originally java-ebuilder would not print Maven’s output to standard output. Redirect Maven’s output to /dev/null, which simulates the common practice of discarding a program’s output on Unix with a command such as mvn help:effective-pom > /dev/null. This can be done with the ProcessBuilder.redirectOutput method:
🌐
ZetCode
zetcode.com › java › processbuilder
Java ProcessBuilder - creating processes in Java
May 1, 2025 - In order to run a command on Windows machine, we could use the following: processBuilder.command("cmd.exe", "/c", "ping -n 3 google.com"). ... The process is lauched with start. try (var reader = new BufferedReader( new InputStreamReader(process.getInputStream()))) { With the getInputStream method we get the input stream from the standard output of the process. $ java Main.java Február 2022 Ne Po Ut St Št Pi So 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
Top answer
1 of 1
24

You have provided very few details in your question, so I can only provide a general answer.

All processes have three standard streams: standard input, standard output and standard error. Standard input is used for reading in data, standard output for writing out data, and standard error for writing out error messages. When you start an external program using Runtime.getRuntime().exec() or ProcessBuilder, Java will create a Process object for the external program, and this Process object will have methods to access these streams.

These streams are accessed as follows:

  • process.getOutputStream(): return the standard input of the external program. This is an OutputStream as it is something your Java code will write to.
  • process.getInputStream(): return the standard output of the external program. This is an InputStream as it is something your Java code will read from.
  • process.getErrorStream(): return the standard error of the external program. This is an InputStream as, like standard output, it is something your Java code will read from.

Note that the names of getInputStream() and getOutputStream() can be confusing.

All streams between your Java code and the external program are buffered. This means each stream has a small amount of memory (a buffer) where the writer can write data that is yet to be read by the reader. The writer does not have to wait for the reader to read its data immediately; it can leave its output in the buffer and continue.

There are two ways in which writing to buffers and reading from them can hang:

  • attempting to write data to a buffer when there is not enough space left for the data,
  • attempting to read from an empty buffer.

In the first situation, the writer will wait until space is made in the buffer by reading data out of it. In the second, the reader will wait until data is written into the buffer.

You mention that closing the stream returned by getOutputStream() caused your program to complete successfully. This closes the standard input of the external program, telling it that there will be nothing more for it to read. If your program then completes successfully, this suggests that your program was waiting for more input to come when it was hanging.

It is perhaps arguable that if you do run an external program, you should close its standard input if you don't need to use it, as you have done. This tells the external program that there will be no more input, and so removes the possibility of it being stuck waiting for input. However, it doesn't answer the question of why your external program is waiting for input.

Most of the time, when you run external programs using Runtime.getRuntime().exec() or ProcessBuilder, you don't often use the standard input. Typically, you'd pass whatever inputs you'd need to the external program on the command line and then read its output (if it generates any at all).

Does your external program do what you need it to and then get stuck, apparently waiting for input? Do you ever need to send it data to its standard input? If you start a process on Windows using cmd.exe /k ..., the command interpreter will continue even after the program it started has exited. In this case, you should use /c instead of /k.

Finally, I'd like to emphasise that there are two output streams, standard output and standard error. There can be problems if you read from the wrong stream at the wrong time. If you attempt to read from the external program's standard output while its buffer is empty, your Java code will wait for the external program to generate output. However, if your external program is writing a lot of data to its standard error, it could fill the buffer and then find itself waiting for your Java code to make space in the buffer by reading from it. The end result of this is your Java code and the external program are both waiting for each other to do something, i.e. deadlock.

This problem can be eliminated simply by using a ProcessBuilder and ensuring that you call its redirectErrorStream() method with a true value. Calling this method redirects the standard error of the external program into its standard output, so you only have one stream to read from.

🌐
Baeldung
baeldung.com › home › java › core java › guide to java.lang.processbuilder api
Guide to java.lang.ProcessBuilder API | Baeldung
November 26, 2025 - Java 9 introduced the concept of pipelines to the ProcessBuilder API: public static List<Process> startPipeline​(List<ProcessBuilder> builders) Using the startPipeline method we can pass a list of ProcessBuilder objects. This static method will then start a Process for each ProcessBuilder. Thus, creating a pipeline of processes which are linked by their standard output and standard input streams...
🌐
Oracle
docs.oracle.com › javase › 7 › docs › api › java › lang › ProcessBuilder.html
ProcessBuilder (Java Platform SE 7 )
If the destination is set to any other value, then Process.getInputStream() will return a null input stream. ... IllegalArgumentException - if the redirect does not correspond to a valid destination of data, that is, has type READ ... Sets this process builder's standard error destination. Subprocesses subsequently started by this object's start() method send their standard error to this destination. If the destination is Redirect.PIPE (the initial value), then the error output of a subprocess can be read using the input stream returned by Process.getErrorStream().
🌐
Oracle
docs.oracle.com › en › java › javase › 11 › docs › api › java.base › java › lang › ProcessBuilder.html
ProcessBuilder (Java SE 11 & JDK 11 )
January 20, 2026 - The standard input of all processes except the first process are null output streams The standard output of all processes except the last process are null input streams. The redirectErrorStream() of each ProcessBuilder applies to the respective process.
Top answer
1 of 1
1

You are probably experiencing a race condition: after writing the command to the shell, your Java program continues to run, and almost immediately calls reader.ready(). The command you wanted to execute has probably not yet output anything, so the reader has no data available. An alternative explanation would be that the command does not write anything to stdout, but only to stderr (or the shell, maybe it has failed to start the command?). You are however not reading from stderr in practice.

To properly handle output and error streams, you cannot check reader.ready() but need to call readLine() (which waits until data is available) in a loop. With your code, even if the program would come to that point, you would read only exactly one line from the output. If the program would output more than one line, this data would get interpreted as the output of the next command. The typical solution is to read in a loop until readLine() returns null, but this does not work here because this would mean your program would wait in this loop until the shell terminates (which would never happen, so it would just hang infinitely). Fixing this would be pretty much impossible, if you do not know exactly how many lines each command will write to stdout and stderr.

However, your complicated approach of using a shell and sending commands to it is probably completely unnecessary. Starting a command from within your Java program and from within the shell is equally fast, and much easier to write. Similarly, there is no performance difference between Runtime.exec() and ProcessBuilder (the former just calls the latter), you only need ProcessBuilder if you need its advanced features.

If you are experiencing performance problems when calling external programs, you should find out where they are exactly and try to solve them, but not with this approach. For example, normally one starts a thread for reading from both the output and the error stream (if you do not start separate threads and the command produces large output, everything might hang). This could be slow, so you could use a thread pool to avoid repeated spawning of processes.

Find elsewhere
🌐
Ongres
ongres.com › blog › java-processes-and-streams
OnGres | Java, Processes and Streams
Quite some boilerplate code is involved when it comes to handle reading and writing from and to an InputStream and OutputStream. We also relied on CompletableFuture to make things a bit more agile –this would have become much more complex for an older Java version. ProcessBuilder sedBuilder = new ProcessBuilder("sed", "s/world/process/"); Process sed = sedBuilder.start(); InputStream inputStream = new ByteArrayInputStream("hello\nworld".getBytes(StandardCharsets.UTF_8)); CompletableFuture<Void> sedInput = CompletableFuture.runAsync(() -> { try { try { byte[] buffer = new byte[8192]; while (t
Top answer
1 of 1
1

There are several problems.

First: gksudo(1) does some dirty, non-standard tricks with the standard input and standard output of the commands it starts. It fails horrible. A good example is this command line:

$ echo foo | gksudo -g cat

I would expect any output and the termination of the cat as soon as the echo has delivered the data. Nope. Both gksudo and cat hang around forever. No output.

Your usecase would be

echo y |gksudo apt-get install ....

and this will not work also. As long as this is not solved, you can forget to do any remote control if the started program requires any user input.

Second: As already pointed out by Roger waitFor() waits for the termination of the command. This will not happen any time soon without any user input and with the gksudo problem.

Third After shoving waitFor down a bit there is the next blocker: You wait for the complete output of the process up to and including the EOF. This will not happen anytime soon (see "first" and "second").

Fourth Only after the process is already dead twice (see "second" and "third") it might get some input - your Y (which might also need an additional \n).


Instead of solving this bunch of problems there might be a better and much easier way: Don't try to control apt-get install with standard input. Just give it some appropriate options which automatically "answers" your questions. A quick man apt-get turns up some candidates:

-y, --yes, --assume-yes
--force-yes
--trivial-only
--no-remove
--no-upgrade

See the manual for details.

I think this is the better and more stable way.

PS: Right now I'm pi*** o*** gksudo quite a bit, so excuse the rant above.

🌐
Java
download.java.net › java › early_access › valhalla › docs › api › java.base › java › lang › Process.html
Process (Java SE 23 & JDK 23 [build 1])
Returns the input stream connected to the normal output of the process. The stream obtains data piped from the standard output of the process represented by this Process object. If the standard output of the process has been redirected using ProcessBuilder.redirectOutput then this method will ...
🌐
Stack Overflow
stackoverflow.com › questions › 60577441 › java-processbuilder-outputstream
sockets - Java Processbuilder OutputStream - Stack Overflow
Socket s = new Socket(ip, Main.CMD_PORT); BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); while(!closed) { String line; if(s.getInputStream().available() > 0 && (line = br.readLine()) != null) textArea.append(line + "\n"); if(buffer != "") { s.getOutputStream().write(buffer.getBytes()); buffer = ""; } } System.out.println("END"); s.close(); dispose();
Top answer
1 of 5
7

Exception in thread "main" java.io.IOException: The pipe has been ended

This means the process you have started has died. I suggest you read the output to see why. e.g. did it give you an error.

2 of 5
6

Is there a reason you are using DataInputStream to read a simple text file? From the Java documentation

A data input stream lets an application read primitive Java data types from an underlying input stream in a machine-independent way

It's possible that the way you are reading the file causes an EOF to be sent to the outputstream causing the pipe to end before it gets to your string.

You requirements seems to be to read the file simply to append to it before passing it on to the wkhtmltoimage process.

You're also missing a statement to close the outputstream to the process. This will cause the process to wait (hang) until it gets an EOF from the input stream, which would be never.

I'd recommend using a BufferedReader instead, and writing it directly to the outputstream before appending your additional string. Then call close() to close the stream.

ProcessBuilder pb = new ProcessBuilder(full_path, " - ", image_save_path);
pb.redirectErrorStream(true);

Process process = null;
try {
    process = pb.start();
} catch (IOException e) {
    System.out.println("Couldn't start the process.");
    e.printStackTrace();
}

System.out.println("reading");

try {
    if (process != null) {
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));

        BufferedReader inputFile = new BufferedReader(new InputStreamReader(new FileInputStream(currentDirectory+"\\bin\\template.txt")));

        String currInputLine = null;
        while((currInputLine = inputFile.readLine()) != null) {
            bw.write(currInputLine);
            bw.newLine();
        }
        bw.write("<body><div id='chartContainer'><small>Loading chart...</small></div></body></html>");
        bw.newLine();
        bw.close();
    }
} catch (IOException e) {
    System.out.println("Either couldn't read from the template file or couldn't write to the OutputStream.");
    e.printStackTrace();
}

BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));

String currLine = null;
try {
    while((currLine = br.readLine()) != null) {
        System.out.println(currLine);
    }
} catch (IOException e) {
    System.out.println("Couldn't read the output.");
    e.printStackTrace();
}
🌐
Oracle
docs.oracle.com › javase › 10 › docs › api › java › lang › ProcessBuilder.html
ProcessBuilder (Java SE 10 & JDK 10 )
The standard input of all processes except the first process are null output streams The standard output of all processes except the last process are null input streams. The redirectErrorStream() of each ProcessBuilder applies to the respective process.
🌐
Java
download.java.net › java › early_access › panama › docs › api › java.base › java › lang › ProcessBuilder.html
ProcessBuilder (Java SE 19 & JDK 19 [build 1])
The standard input of all processes except the first process are null output streams The standard output of all processes except the last process are null input streams. The redirectErrorStream() of each ProcessBuilder applies to the respective process.
🌐
Stack Overflow
stackoverflow.com › questions › 38143783 › how-to-redirect-the-output-to-a-java-stream-using-java-lang-processbuilder
How to redirect the output to a java stream using java.lang.ProcessBuilder - Stack Overflow
July 1, 2016 - ProcessBuilder processBuilder = ProcessBuilder("/here/your/command1", "/here/your/command2"); Process process = processBuilder.start(); OutputStream outputSteeam = process.getOutputStream(); ... Sign up to request clarification or add additional ...
Top answer
1 of 2
62

The Process OutputStream (our point of view) is the STDIN from the process point of view

OutputStream stdin = process.getOutputStream(); // write to this

So what you have should be correct.

My driver (apply your own best practices with try-with-resources statements)

public class ProcessWriter {
    public static void main(String[] args) throws Exception {
        ProcessBuilder builder = new ProcessBuilder("java", "Test");
        builder.directory(new File("C:\\Users\\sotirios.delimanolis\\Downloads"));
        Process process = builder.start();

        OutputStream stdin = process.getOutputStream(); // <- Eh?
        InputStream stdout = process.getInputStream();

        BufferedReader reader = new BufferedReader(new InputStreamReader(stdout));
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stdin));

        writer.write("Sup buddy");
        writer.flush();
        writer.close();

        Scanner scanner = new Scanner(stdout);
        while (scanner.hasNextLine()) {
            System.out.println(scanner.nextLine());
        }
    }
}

My application

public class Test {

    public static void main(String[] args) throws Exception {
        Scanner console = new Scanner(System.in);
        System.out.println("heello World");
        while(console.hasNextLine()) {
            System.out.println(console.nextLine());
        }
    }
}

Running the driver prints

heello World
Sup buddy

For some reason I need the close(). The flush() alone won't do it.

Edit It also works if instead of the close() you provide a \n.

So with

writer.write("Sup buddy");
writer.write("\n");
writer.write("this is more\n");
writer.flush();    

the driver prints

heello World
Sup buddy
this is more
2 of 2
6

This is an example which maybe can helps someone

import java.io.IOException;
import java.io.File;
import java.io.OutputStream;
import java.io.InputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) throws IOException {
        String[] commands = {"C:/windows/system32/cmd.exe"};
        ProcessBuilder builder = new ProcessBuilder(commands);
        builder.directory(new File("C:/windows/system32"));
        Process process = builder.start();

        OutputStream stdin = process.getOutputStream();
        InputStream stdout = process.getInputStream();
        InputStream stderr = process.getErrorStream();

        BufferedReader reader = new BufferedReader(new InputStreamReader(stdout));
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stdin));
        BufferedReader error = new BufferedReader(new InputStreamReader(stderr));

        new Thread(() -> {
            String read;
            try {
                while ((read = reader.readLine()) != null) {
                    System.out.println(read);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            String read;
            try {
                while ((read = error.readLine()) != null) {
                    System.out.println(read);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            while (true) {
                try {
                    Scanner scanner = new Scanner(System.in);
                    writer.write(scanner.nextLine());
                    writer.newLine();
                    writer.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}