|
|
|
In this article I explain how Java 26 simplifies Process resource management
Published February 2026, Author Jack Shirazi
In Java 26 with JDK-8364361, java.lang.Process has been updated
so it implements AutoCloseable/Closeable with a new close() method. A process can now be used in try-with-resources and closed automatically at scope exit.
Closing a process ensures that it has terminated and its streams and underlying resources are released. When used with try-with-resources, the process and its streams are closed when the try-with-resources block exits.
For many of you this is all the information you need, and you can stop reading now. The rest of this article shows an example of good generic process handling code, and how that can be simplified by Java 26
The following example wraps process execution to:
The code in bold is what changes for Java 26 - most of that can be eliminated entirely, resulting in simpler and likely safer code.
private volatile int exitValue;public StringBuilder executeCommand(int timeoutInSeconds, String[] command, Map<String, String> envs) throws IOException { try { exitValue = -1; System.out.println("Trying to execute with timeout = " + timeoutInSeconds + " seconds, command = " + Arrays.toString(command)); ProcessBuilder pb = new ProcessBuilder(command); if (envs != null) { Map<String, String> env = pb.environment(); for (String setEnv : envs.keySet()) { env.put(setEnv, envs.get(setEnv)); } } // Merge stdout and stderr so I only have to read one stream. pb.redirectErrorStream(true);
// Set up infrastructure for the process management StringBuilder commandOutput = new StringBuilder(); long timeout = timeoutInSeconds * 1000L; long start = System.currentTimeMillis(); long now = start; boolean isAlive = true; byte[] buffer = new byte[64 * 1000];
Process p = pb.start(); //can't be in the try-block
try (InputStream in = p.getInputStream()) { // Stop trying if the time elapsed exceeds the timeout. //stop trying if the time elapsed exceeds the timeout while (isAlive && (now - start) < timeout) { while (in.available() > 0) { int lengthRead = in.read(buffer, 0, buffer.length); commandOutput.append(new String(buffer, 0, lengthRead)); } try {Thread.sleep(1000L);} catch (InterruptedException e) {Thread.currentThread().interrupt();} now = System.currentTimeMillis(); // If it's not alive but there is still readable input, then continue reading. isAlive = p.isAlive() || in.available() > 0; } }
//Cleanup as well as I can. All this bold part is unnecessary from Java 26 (unless explicit shutdown handling is desired) boolean exited = false; try {exited = p.waitFor(3, TimeUnit.SECONDS);}catch (InterruptedException e) {Thread.currentThread().interrupt();} if (!exited) { p.destroy(); try {Thread.sleep(1000L);} catch (InterruptedException e) {Thread.currentThread().interrupt();} if (p.isAlive()) { p.destroyForcibly(); try {p.waitFor(3, TimeUnit.SECONDS);}catch (InterruptedException e) {Thread.currentThread().interrupt();} } } if (!p.isAlive()) { exitValue = p.exitValue(); } if ((now - start) > timeout) { throw new TimedOutException("Timed out: timeout of "+timeoutInSeconds+" seconds, command = "+Arrays.toString(command)); } return commandOutput; } catch (TimedOutException e) { // Needed as TimedOutException is preferable to the more generic CommandFailedException throw e; } catch (IOException e) { throw new CommandFailedException("Command failed, command = "+Arrays.toString(command), e); } }
The same example as above but simplified to use the Java 26 Process AutoCloseable feature.
private volatile int exitValue;public StringBuilder executeCommand(int timeoutInSeconds, String[] command, Map<String, String> envs) throws IOException { try { exitValue = -1; System.out.println("Trying to execute with timeout = " + timeoutInSeconds + " seconds, command = " + Arrays.toString(command)); ProcessBuilder pb = new ProcessBuilder(command); if (envs != null) { Map<String, String> env = pb.environment(); for (String setEnv : envs.keySet()) { env.put(setEnv, envs.get(setEnv)); } } // Merge stdout and stderr so I only have to read one stream. pb.redirectErrorStream(true);
// Set up infrastructure for the process management StringBuilder commandOutput = new StringBuilder(); long timeout = timeoutInSeconds * 1000L; long start = System.currentTimeMillis(); long now = start; boolean isAlive = true; byte[] buffer = new byte[64 * 1000];
// Java 26: Process is AutoCloseable/Closeable. The start() is moved into the try block try (Process p = pb.start(); InputStream in = p.getInputStream()) { // Stop trying if the time elapsed exceeds the timeout. while (isAlive && (now - start) < timeout) { while (in.available() > 0) { int lengthRead = in.read(buffer, 0, buffer.length); commandOutput.append(new String(buffer, 0, lengthRead)); } try {Thread.sleep(1000L);} catch (InterruptedException e) {Thread.currentThread().interrupt();} now = System.currentTimeMillis(); // If it's not alive but there is still readable input, then continue reading. isAlive = p.isAlive() || in.available() > 0; }
if (!p.isAlive()) { exitValue = p.exitValue(); } if ((now - start) > timeout) { throw new TimedOutException("Timed out: timeout of " + timeoutInSeconds + " seconds, command = " + Arrays.toString(command)); } return commandOutput; } } catch (TimedOutException e) { // Needed as TimedOutException is preferable to the more generic CommandFailedException throw e; } catch (IOException e) { throw new CommandFailedException("Command failed, command = " + Arrays.toString(command), e); } }
The Java 26 change does not replace execution logic, timeout checks, or error mapping. It mainly simplifies process resource management by letting the language handle close/cleanup at block exit