1. O que são processos no SO e por que executá-los a partir do Java
Processos: da JVM ao bash e vice-versa
Quando você liga o computador e abre um navegador, um aplicativo de mensagens ou um jogo — tudo isso são processos separados. O sistema operacional inicia para cada programa seu próprio “mini-mundo”: aloca memória, tempo de CPU, permite trabalhar com arquivos e rede. Assim, os programas coexistem sem atrapalhar uns aos outros.
Um programa Java não é exceção. Quando você executa java MyApp, o sistema cria para ele um processo separado com todos os recursos necessários. Dentro dele, seu programa já está em execução: calcula, desenha, lê arquivos — tudo o que for necessário.
Mas às vezes só Java não basta. Às vezes é preciso pedir ajuda a outro programa — iniciar um compactador para empacotar arquivos, chamar o ffmpeg para processar um vídeo, ou simplesmente descobrir qual versão do Java está instalada no computador. Isso é a execução de um processo externo: o Java diz ao sistema “execute esta utilidade para mim” e depois obtém o resultado do seu trabalho.
Essencialmente, é uma forma de tornar o programa mais flexível: combinar ferramentas diferentes, automatizar tarefas repetitivas ou incorporar sua lógica a processos do sistema já existentes. Às vezes é mais simples pedir que um comando externo faça parte do trabalho do que reinventar a roda no código.
JVM vs. processo externo
Processo da JVM — seu programa em execução na Máquina Virtual Java.
Processo externo — qualquer outro programa: calculadora, script Python, linha de comando, até outro processo Java.
2. A classe ProcessBuilder
Nos “tempos antigos” o Java executava processos via o método Runtime.getRuntime().exec(). Não era a maneira mais conveniente nem segura — como tentar pregar um prego com um microscópio. Por isso, a partir do Java 5 surgiu a classe ProcessBuilder, que permite criar, configurar e iniciar processos externos de forma mais flexível e clara.
ProcessBuilder — é como um “construtor” que permite definir antecipadamente todos os parâmetros do processo: comando, argumentos, diretório de trabalho, variáveis de ambiente etc.
Sintaxe: criação de processo
ProcessBuilder pb = new ProcessBuilder("comando", "argumento1", "argumento2", ...);
- O primeiro argumento — o nome do comando (por exemplo, "ls", "dir", "ping", "java").
- Os demais — os parâmetros do comando.
Exemplo: executar o comando ls (Linux/Mac) ou dir (Windows)
ProcessBuilder pb;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
pb = new ProcessBuilder("cmd.exe", "/c", "dir");
} else {
pb = new ProcessBuilder("ls", "-l");
}
Aliás, no Windows comandos como dir, copy etc. — não são executáveis separados, mas comandos “integrados” à linha de comando (cmd.exe). Por isso devem ser executados via cmd.exe /c ....
Exemplo: iniciar um processo simples
ProcessBuilder pb = new ProcessBuilder("echo", "Hello, Java!");
3. Configuração do ambiente do processo
Passagem de argumentos. Os argumentos do comando são passados como strings separadas:
ProcessBuilder pb = new ProcessBuilder("ping", "google.com");
Definição do diretório de trabalho. Por padrão, o processo é iniciado na mesma pasta que o seu programa. Mas é possível indicar explicitamente outro diretório:
pb.directory(new java.io.File("/tmp")); // Para Linux/Mac
pb.directory(new java.io.File("C:\\Temp")); // Para Windows
Alterando variáveis de ambiente. Cada processo tem seu próprio conjunto de variáveis de ambiente. É possível adicioná-las ou modificá-las:
pb.environment().put("MY_VAR", "HelloFromJava");
Isso pode ser útil se o processo externo espera variáveis específicas.
4. Iniciando o processo
Método start(). Quando tudo estiver configurado, é hora de iniciar o processo:
Process process = pb.start();
O método start() retorna um objeto Process, que permite gerenciar o programa iniciado: ler sua saída, escrever em sua entrada, encerrá-lo etc.
Tratamento de exceções. start() pode lançar IOException se o comando não for encontrado, se não houver permissões ou ocorrer outro erro de inicialização.
Exemplo:
try {
Process process = pb.start();
// Trabalhamos com o processo...
} catch (IOException e) {
System.out.println("Erro ao iniciar o processo: " + e.getMessage());
}
5. Prática: iniciando comandos simples
Exemplo 1: listar os arquivos na pasta
import java.io.*;
public class ProcessDemo {
public static void main(String[] args) {
// Definimos o comando de acordo com o SO
ProcessBuilder pb;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
pb = new ProcessBuilder("cmd.exe", "/c", "dir");
} else {
pb = new ProcessBuilder("ls", "-l");
}
try {
Process process = pb.start();
// Lemos a saída do processo (stdout)
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream())
);
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
// Aguardamos a finalização do processo
int exitCode = process.waitFor();
System.out.println("Processo finalizado com código: " + exitCode);
} catch (IOException | InterruptedException e) {
System.out.println("Erro: " + e.getMessage());
}
}
}
O que acontece aqui?
- Detectamos qual é o SO — para escolher o comando correto.
- Criamos o ProcessBuilder com o comando necessário.
- Iniciamos o processo com start().
- Lemos as linhas do stdout do processo e as exibimos na tela.
- Aguardamos a finalização do processo (waitFor()).
- Exibimos o código de saída (0 — sucesso; outro valor — erro).
Exemplo 2: executar java -version
ProcessBuilder pb = new ProcessBuilder("java", "-version");
try {
Process process = pb.start();
// java -version escreve em stderr, então lemos getErrorStream()
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getErrorStream())
);
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
process.waitFor();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
Detalhe importante: Alguns comandos (por exemplo, java -version) não escrevem na saída padrão (stdout), mas no fluxo de erros (stderr). Portanto, às vezes é preciso ler process.getErrorStream().
6. Multiplataforma: diferenças entre Windows e Linux/Mac
- Os comandos e seus parâmetros podem ser diferentes.
- Os caminhos de arquivos são escritos de forma diferente (C:\Temp vs /tmp).
- Alguns comandos (por exemplo, ls, cat) existem apenas em sistemas tipo Unix, e no Windows há equivalentes (dir, type).
- No Windows, comandos integrados são executados apenas via cmd.exe /c comando.
Exemplo de verificação do SO:
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
// Windows
} else if (os.contains("mac")) {
// macOS
} else if (os.contains("nix") || os.contains("nux")) {
// Linux
}
Dica: Sempre teste seus programas nos SOs-alvo se você precisa de compatibilidade multiplataforma.
7. Tabela: principais métodos e recursos do ProcessBuilder
| Método/campo | Finalidade | Exemplo de uso |
|---|---|---|
|
Criar um processo com comando e argumentos | |
|
Definir o diretório de trabalho | |
|
Obter/alterar variáveis de ambiente | |
|
Iniciar o processo | |
|
Obter o stdout do processo | |
|
Obter o stderr do processo | |
|
Obter o stdin do processo | |
|
Aguardar a finalização do processo | |
|
Obter o código de saída do processo | |
8. Erros comuns ao iniciar processos externos
Erro nº 1: comando não encontrado. Se você errou o nome do comando ou ele não existe no sistema, receberá IOException: Cannot run program .... Por exemplo, tentar executar ls no Windows.
Erro nº 2: passagem incorreta de argumentos. Não junte todo o comando em uma única string! Correto: new ProcessBuilder("ping", "google.com"). Incorreto: new ProcessBuilder("ping google.com").
Erro nº 3: não considerar as diferenças do SO. Um comando que funciona muito bem no Linux pode não existir no Windows e vice-versa. Sempre verifique o SO e adapte o comando.
Erro nº 4: não tratar a saída do processo. Se você não ler a saída do processo, ele pode “travar” por causa do estouro de buffer. Mesmo que você não vá usar a saída — leia-a e, por exemplo, apenas descarte.
Erro nº 5: não fechar os streams. Os streams do processo precisam ser fechados após o uso para evitar vazamentos de recursos.
Erro nº 6: não tratar as exceções. Iniciar um processo externo é arriscado. Sempre use try-catch e informe o usuário sobre os erros.
GO TO FULL VERSION