1. Deadlock durante la lettura/scrittura degli stream
Uno degli errori più spiacevoli è quando un processo si blocca e non termina, anche se sembra che tutto sia stato fatto correttamente. Una causa frequente è il riempimento del buffer di output o gli errori del processo esterno. Se non si leggono entrambi gli stream (stdout e stderr), il processo può «bloccarsi» quando tenta di scrivere qualcosa, perché nessuno svuota i dati dal suo buffer.
Esempio di problema
ProcessBuilder builder = new ProcessBuilder("java", "-version");
Process process = builder.start();
// Leggiamo solo stdout e ignoriamo stderr!
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
process.waitFor();
Se il comando scrive qualcosa in stderr (per esempio, java -version quasi sempre scrive lì!), questo stream si riempie e il processo si blocca.
Come fare correttamente
Leggete entrambi gli stream in parallelo (tramite thread separati o ExecutorService):
ProcessBuilder builder = new ProcessBuilder("java", "-version");
Process process = builder.start();
// Leggiamo 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();
}
});
// Leggiamo 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();
Conclusione:
Se non leggi stderr — il processo può bloccarsi.
Se non leggi stdout — può bloccarsi comunque.
Leggi entrambi gli stream — e sarai a posto!
2. Problemi di codifica dei caratteri
I processi esterni possono usare una codifica del testo diversa da quella predefinita della tua applicazione Java. Se non specifichi la codifica corretta, otterrai testo illeggibile al posto dei caratteri (è particolarmente evidente con il cirillico).
Esempio di errore
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
Questo codice usa la codifica di sistema predefinita. Ma se il processo esterno scrive, per esempio, in UTF-8, mentre tu usi Windows-1251, il testo in cirillico diventerà incomprensibile.
Come fare correttamente
Passa esplicitamente la codifica necessaria, se la conosci:
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)
);
Se non sei sicuro — consulta la documentazione del programma oppure prova alcune opzioni.
Suggerimento: su Windows le utility da console spesso usano CP866 o Windows-1251, mentre su Linux — UTF-8.
3. Differenze tra piattaforme
I comandi che funzionano su un sistema operativo possono mancare su un altro. Per esempio, ls è presente su Linux/Mac, ma non su Windows (dove c’è dir). Differiscono anche la sintassi dei comandi, i separatori dei percorsi e le virgolette.
Esempio di errore
ProcessBuilder builder = new ProcessBuilder("ls", "-l");
builder.start(); // Su Windows: "ls" non trovato!
Come fare correttamente
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");
}
Percorso dei file: usa File.separator invece di «/» o «\», per evitare problemi con i percorsi.
4. Problemi di permessi
Alcuni comandi richiedono privilegi di amministratore (eliminazione di file di sistema, modifica delle impostazioni di rete, ecc.). Se i privilegi sono insufficienti, il comando terminerà con errore o non partirà affatto.
Esempio
ProcessBuilder builder = new ProcessBuilder("rm", "-rf", "/root/secret.txt");
Process process = builder.start();
// ... aspettiamo l'errore Permission denied
Come fare correttamente
- Verifica se il tuo comando richiede privilegi elevati.
- Gestisci il codice di uscita del processo tramite process.exitValue().
- Leggi stderr — lì di solito trovi la causa dell’errore (ad esempio, «Permission denied»).
5. Perdite di risorse
Se non chiudi gli stream del processo (InputStream, OutputStream, ErrorStream), possono restare aperti, consumare risorse e persino bloccare la terminazione. Analogamente, se non termini il processo stesso, può diventare uno «zombie» nel sistema.
Esempio di errore
Process process = builder.start();
// ... lavoriamo, ma non chiudiamo gli stream!
Come fare correttamente
Usa try-with-resources per gli stream:
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
// Leggiamo l'output
}
Dopo aver finito di lavorare con il processo, interrompilo correttamente:
process.destroy(); // Terminare il processo (se è ancora vivo)
Attenzione: se non chiudi gli stream possono verificarsi perdite di memoria, blocchi e problemi di sistema (soprattutto con l’avvio massivo di processi).
6. Deadlock durante l’interazione interattiva
Nello scambio interattivo è facile incappare in una situazione di stallo: Java attende una risposta e il processo esterno attende i tuoi dati. Di conseguenza, entrambi restano in attesa. Oppure invii un messaggio ma non leggi la risposta — a un certo punto il buffer del programma esterno si riempie e questo smette di scrivere.
Per evitarlo, separa le responsabilità: un thread si occupa della lettura, un altro della scrittura. Così l’interazione procede in parallelo e nessuno blocca l’altro. Inoltre, non lasciare stream aperti dopo la fine — chiudili, affinché il sistema non mantenga risorse inutili.
GO TO FULL VERSION