1. Deadlock ao ler/gravar fluxos
Um dos problemas mais desagradáveis é quando o processo trava e não termina, embora pareça que tudo está certo. Uma causa comum é o estouro do buffer de saída ou de erros do processo externo. Se você não ler ambos os fluxos (stdout e stderr), o processo pode “ficar bloqueado” ao tentar imprimir algo, porque ninguém está esvaziando o buffer dele.
Exemplo do problema
ProcessBuilder builder = new ProcessBuilder("java", "-version");
Process process = builder.start();
// Lemos apenas stdout e ignoramos stderr!
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
process.waitFor();
Se o comando escrever algo em stderr (por exemplo, java -version quase sempre escreve lá!), esse fluxo enche e o processo trava.
Como fazer corretamente
Leia ambos os fluxos em paralelo (com threads separadas ou ExecutorService):
ProcessBuilder builder = new ProcessBuilder("java", "-version");
Process process = builder.start();
// Lemos stdout
Thread stdoutThread = new Thread(() -> {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println("[stdout] " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
});
// Lemos stderr
Thread stderrThread = new Thread(() -> {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getErrorStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.err.println("[stderr] " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
});
stdoutThread.start();
stderrThread.start();
process.waitFor();
stdoutThread.join();
stderrThread.join();
Conclusão:
Se você não ler stderr — o processo pode travar.
Se você não ler stdout — ele também pode travar.
Leia ambos os fluxos — e tudo ficará bem!
2. Problemas de codificação de caracteres
Processos externos podem usar uma codificação diferente para a saída de texto do que a padrão da sua aplicação Java. Se não indicar a codificação correta, você verá caracteres ilegíveis em vez de texto (isso é especialmente perceptível com caracteres cirílicos).
Exemplo de erro
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
Esse código utiliza a codificação padrão do sistema. Mas se o processo externo escrever, por exemplo, em UTF-8, e o seu estiver em Windows-1251, o texto em cirílico vira uma bagunça.
Como fazer corretamente
Informe explicitamente a codificação necessária, se você a souber:
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)
);
Se não tiver certeza — consulte a documentação do programa ou experimente algumas opções.
Dica: no Windows, utilitários de console frequentemente usam CP866 ou Windows-1251, e no Linux — UTF-8.
3. Diferenças entre plataformas
Comandos que funcionam em um sistema operacional podem não existir em outro. Por exemplo, ls existe no Linux/Mac, mas não no Windows (lá — dir). Também diferem a sintaxe dos comandos, os separadores de caminho e as aspas.
Exemplo de erro
ProcessBuilder builder = new ProcessBuilder("ls", "-l");
builder.start(); // No Windows: "ls" não encontrado!
Como fazer corretamente
String os = System.getProperty("os.name").toLowerCase();
ProcessBuilder builder;
if (os.contains("win")) {
builder = new ProcessBuilder("cmd", "/c", "dir");
} else {
builder = new ProcessBuilder("ls", "-l");
}
Caminho para arquivos: use File.separator em vez de “/” ou “\” para não ter problemas com caminhos.
4. Problemas de permissão
Alguns comandos exigem privilégios de administrador (exclusão de arquivos do sistema, alteração de configurações de rede etc.). Sem permissões suficientes, o comando terminará com erro ou nem será iniciado.
Exemplo
ProcessBuilder builder = new ProcessBuilder("rm", "-rf", "/root/secret.txt");
Process process = builder.start();
// ... aguardando erro Permission denied
Como fazer corretamente
- Verifique se o seu comando exige privilégios elevados.
- Trate o código de retorno do processo via process.exitValue().
- Leia o stderr — ali costuma estar a causa do erro (por exemplo, “Permission denied”).
5. Vazamentos de recursos
Se você não fechar os fluxos do processo (InputStream, OutputStream, ErrorStream), eles podem “ficar pendurados”, consumir recursos e até bloquear o encerramento. Da mesma forma, se você não encerrar o próprio processo, ele pode virar um “zumbi” no sistema.
Exemplo de erro
Process process = builder.start();
// ... trabalhamos, mas não fechamos os fluxos!
Como fazer corretamente
Use try-with-resources para os fluxos:
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
// Lemos a saída
}
Depois de terminar o trabalho com o processo, finalize-o corretamente:
process.destroy(); // Encerrar o processo (se ainda estiver vivo)
Atenção: se você não fechar os fluxos — podem ocorrer vazamentos de memória, travamentos e problemas no sistema (especialmente ao iniciar muitos processos).
6. Deadlock na comunicação interativa
Em uma troca interativa é fácil cair em bloqueio mútuo: a aplicação Java espera uma resposta, e o processo externo — seus dados. No fim, ambos “ficam em silêncio”. Ou você enviou uma mensagem, mas não lê a resposta — em algum momento o buffer do programa externo enche e ele para de escrever.
Para evitar isso, separe as responsabilidades: uma thread cuida da leitura e outra da escrita. Assim, a interação ocorre em paralelo e ninguém bloqueia ninguém. Também não deixe fluxos abertos após o término — feche-os para que o sistema não retenha recursos desnecessários.
GO TO FULL VERSION