1. Fluxos de entrada/saída do processo
Quando você inicia um processo externo a partir do Java, abre-se um verdadeiro mini-diálogo entre ele e o seu programa — três canais de comunicação.
O primeiro canal é a saída padrão. Tudo o que o processo externo quer mostrar “na tela” você pode capturar por meio de Process.getInputStream(). O segundo canal é o fluxo de erros. Se o processo decidir reclamar ou emitir uma mensagem de erro, você saberá disso através de Process.getErrorStream(). E, por fim, o terceiro é a entrada padrão. Por meio dele, você pode enviar dados ao processo, caso ele esteja aguardando algo de você. Isso é feito com Process.getOutputStream().
É um pouco confuso que os nomes dos fluxos pareçam invertidos. Para o processo, InputStream é aquilo que você lê (a saída dele), e OutputStream é para onde você escreve (a entrada dele). Mas se olharmos pelos olhos do seu programa Java, tudo fica lógico: você lê o que o processo produz e escreve o que quer enviar para ele.
Esquema dos fluxos do processo
+-------------------+ +---------------------+
| Seu programa | <-------> | Processo externo |
+-------------------+ +---------------------+
| |
| getOutputStream() --------> stdin
| getInputStream() <-------- stdout
| getErrorStream() <-------- stderr
2. Leitura da saída do processo (stdout)
Vamos aprender a ler o que o processo externo imprime na tela. Pode ser o resultado de um comando, a versão de um programa, uma mensagem de sucesso — qualquer coisa.
Exemplo 1: Lendo a saída do comando java -version
Primeiro, vamos iniciar o processo e depois ler a sua saída.
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
public class ProcessOutputExample {
public static void main(String[] args) throws IOException {
// Criamos o processo: o comando depende do seu SO!
ProcessBuilder pb = new ProcessBuilder("java", "-version");
Process process = pb.start();
// Lemos o fluxo de erros (java -version geralmente escreve lá!)
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getErrorStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("Process output: " + line);
}
}
}
}
Atenção! O comando java -version escreve seu resultado não no stdout normal, mas no fluxo de erros stderr. Isso não é bug, é uma particularidade do Java. Por isso lemos process.getErrorStream(). Para a maioria dos outros comandos (por exemplo, echo, ls, dir) — use getInputStream().
Exemplo 2: Lendo a saída do comando echo
ProcessBuilder pb = new ProcessBuilder("echo", "Hello, Java!");
Process process = pb.start();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("Process output: " + line);
}
}
Por que precisamos de BufferedReader?
O fluxo de entrada padrão é um fluxo de bytes (InputStream). Para lê-lo como linhas, usamos a combinação InputStreamReader (converte bytes em caracteres) + BufferedReader (facilita a leitura linha a linha).
3. Escrita no fluxo de entrada do processo (stdin)
Às vezes o processo externo espera dados nossos. Por exemplo, se você executa um script que pede o nome do usuário ou uma calculadora que aguarda números.
Para isso, use process.getOutputStream() — sim, exatamente: você “escreve” no fluxo do processo.
Exemplo 3: Enviando dados para o processo
Suponha que você tenha um script Python simples que lê uma linha e a imprime de volta:
echo_input.py:
# echo_input.py
line = input()
print("You said:", line)
Vamos executá-lo a partir do Java e enviar uma linha:
import java.io.*;
public class ProcessStdinExample {
public static void main(String[] args) throws IOException {
ProcessBuilder pb = new ProcessBuilder("python", "echo_input.py");
Process process = pb.start();
// Escrevemos no stdin do processo
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(process.getOutputStream()))) {
writer.write("Olá do Java!\n");
writer.flush();
}
// Lemos o stdout do processo
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("Resposta do processo: " + line);
}
}
}
}
Não se esqueça de flush() — caso contrário, os dados podem “ficar presos” no buffer e não chegar ao processo.
4. Tratamento de erros: lendo o stderr
Um processo externo pode não apenas nos trazer resultados, mas também “reclamar da vida” — escrever erros. Se você não lê o fluxo de erros, pode perder informações importantes ou até sofrer um bloqueio (deadlock) se o buffer de erros encher.
Exemplo 4: Lendo o stderr do processo
ProcessBuilder pb = new ProcessBuilder("java", "-version");
Process process = pb.start();
// Lemos os erros
try (BufferedReader errorReader = new BufferedReader(
new InputStreamReader(process.getErrorStream()))) {
String line;
while ((line = errorReader.readLine()) != null) {
System.out.println("Erro do processo: " + line);
}
}
Por que é importante ler ambos os fluxos?
Se o processo escreve muito em stdout ou stderr e você não lê esses fluxos, o buffer do SO pode encher e o processo “congelar”, aguardando alguém liberar espaço. Portanto, uma boa prática é ler ambos os fluxos em paralelo (ou pelo menos alternadamente).
5. Prática: executar comandos, ler a saída, tratar erros
Vamos juntar tudo o que vimos em uma pequena utilidade que executa um comando externo, lê sua saída padrão e erros e imprime tudo na tela.
Exemplo 5: Execução de comando universal
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class RunCommand {
public static void main(String[] args) throws Exception {
if (args.length == 0) {
System.out.println("Informe um comando para executar, por exemplo: java RunCommand ls -l");
return;
}
var process = new ProcessBuilder(args).start();
// Iniciamos threads separadas para ler stdout e stderr
var t1 = Thread.ofPlatform().start(() -> readStream(process.inputReader(), "[stdout]"));
var t2 = Thread.ofPlatform().start(() -> readStream(process.errorReader(), "[stderr]"));
int exitCode = process.waitFor(); // aguardamos o término do processo
t1.join();
t2.join();
System.out.println("O processo terminou com o código: " + exitCode);
}
private static void readStream(BufferedReader reader, String prefix) {
try (reader) {
reader.lines().forEach(line -> System.out.println(prefix + " " + line));
} catch (Exception e) {
System.err.println("Erro de leitura " + prefix + ": " + e.getMessage());
}
}
}
Essa abordagem evita deadlock e garante que nenhum erro seja perdido!
6. Um breve resumo sobre codificações
Por padrão, InputStreamReader usa a codificação do sistema. Se o processo externo escreve em outra codificação (por exemplo, UTF-8, e você está em Windows-1251), a saída pode aparecer com caracteres ilegíveis. Informe a codificação explicitamente:
new InputStreamReader(process.getInputStream(), "UTF-8")
7. Exemplo: executar um script Python e enviar dados para ele
Suponha que você tenha um script Python que espera várias linhas na entrada e depois imprime o resultado.
adder.py:
# adder.py
a = int(input())
b = int(input())
print(a + b)
Do Java, você pode enviar os números e ler a resposta:
import java.io.BufferedReader;
import java.nio.charset.StandardCharsets;
public class PythonAdder {
public static void main(String[] args) throws Exception {
var process = new ProcessBuilder("python", "adder.py").start();
// Enviamos dois números para o stdin do script Python
try (var writer = process.outputWriter(StandardCharsets.UTF_8)) {
writer.println("10");
writer.println("25");
}
// Lemos o resultado do stdout usando inputReader
try (BufferedReader reader = process.inputReader(StandardCharsets.UTF_8)) {
reader.lines().forEach(line -> System.out.println("Resposta do Python: " + line));
}
int exitCode = process.waitFor();
System.out.println("O Python terminou com o código: " + exitCode);
}
}
8. Por que ocorre deadlock? Como evitá-lo
Deadlock é a situação em que o processo e o seu programa ficam esperando um ao outro indefinidamente. Por exemplo, se o buffer de saída do processo encher e você não estiver lendo esse fluxo, o processo vai parar. Se você estiver esperando o término do processo e ele estiver esperando que você escreva algo para ele — também é um deadlock.
Como evitar:
- Sempre leia ambos os fluxos (stdout e stderr).
- Para processos longos/verbosos, use threads separadas para leitura.
- Não bloqueie escrita e leitura simultaneamente na mesma thread.
9. Erros comuns ao trabalhar com os fluxos de E/S do processo
Erro nº 1: Ler apenas um dos fluxos (stdout ou stderr).
Se você ler apenas o stdout, e o processo escrever o erro em stderr, o buffer de erros pode encher — o processo vai travar. Solução: leia ambos os fluxos (de preferência em threads separadas).
Erro nº 2: Codificação incorreta.
Se você não especificar a codificação correta, a saída pode ficar ilegível. Sempre verifique em qual codificação o processo externo escreve (por exemplo, defina explicitamente "UTF-8" ou use StandardCharsets.UTF_8).
Erro nº 3: Não fechar os fluxos.
Fluxos abertos podem causar vazamento de recursos. Use try-with-resources ou feche todos os fluxos explicitamente.
Erro nº 4: Não chamar flush() ao escrever no stdin do processo.
Se você esquecer de chamar flush(), os dados podem não chegar ao processo — ele ficará esperando entrada para sempre.
Erro nº 5: Diferenças entre plataformas.
Comandos e sintaxe podem variar entre Windows e Linux/Mac. Verifique o SO via System.getProperty("os.name") e ajuste os comandos conforme necessário.
Erro nº 6: Não tratar exceções.
O trabalho com processos frequentemente envolve IOException. Sempre trate as exceções para que o programa não finalize de forma abrupta.
GO TO FULL VERSION