The book is probably written + tested with Windows 10 or earlier. In earlier versions of Windows, notepad is a native executable with single document interface.
Note that the call r.exec("notepad"); relies on the use of system variable PATHEXT to resolve the binary. On my machine PATHEXT looks at the Path with these file extensions for "notepad".
PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
So the old sample launches notepad.exe as long as not finding another file extension in the path.
Windows 11
Windows 11 notepad is an app from Microsoft Store and has an application alias "Notepad.exe". It appears to launch subprocesses. The code sample you have works but it does not await the children to complete.
Here is example launch which uses ProcessBuilder and awaits child processes to exit:
public static void main(String... args) throws IOException, InterruptedException, ExecutionException {
// Ideally use "notepad.exe" here that you don't accidentally run notepad.com if such exists
start("notepad");
}
public static int start(String ... cmd) throws IOException, InterruptedException, ExecutionException {
System.out.println("start "+Arrays.toString(cmd));
// Launch and wait:
ProcessBuilder pb = new ProcessBuilder(cmd);
pb.redirectErrorStream(true); // No STDERR => merge to STDOUT
Process p = pb.start();
// No STDIN, just closes to signal end of input
// (replace _ by a variable on older JDK)
try(OutputStream _ = p.getOutputStream()) {/*EMPTY*/}
// Process STDOUT
p.getInputStream().transferTo(System.out);
// Await process exit
int rc = p.waitFor();
// Show results + STDOUT+STDERR
System.out.println("Exit PID "+p.pid()+" EXITCODE "+rc +' '+(rc == 0 ? "OK":"**** ERROR ****")+" command:"+Arrays.toString(cmd));
// Await process descendants exit
waitForDescendants(p.toHandle());
// Optional: if (rc != 0) throw new RuntimeException("Process failed code: "+rc+" command: "+Arrays.toString(cmd));
return rc;
}
/**
* Pause current thread if there are any known running subprocesses of handle
*/
private static void waitForDescendants(ProcessHandle handle) throws InterruptedException, ExecutionException {
var waiters = handle.descendants()
.map(ProcessHandle::onExit).toList();
System.out.println("pid "+handle.pid() +" has "+waiters.size()+" descendants");
for (CompletableFuture<ProcessHandle> cf : waiters) {
ProcessHandle subp = cf.get();
System.out.println("waitForDescendants "+handle.pid() +" child ended "+subp.pid());
waitForDescendants(subp);
}
}
The above code should work in Windows 10, and the call to waitForDescendants will do nothing. On Windows 11 it may print details of the subprocess, something like:
start [notepad]
Exit PID 59236 EXITCODE 0 OK command:[notepad]
pid 59236 has 1 descendants
waitForDescendants 59236 child ended 47908
pid 47908 has 0 descendants
InterruptedException, you probably don't want to silently handle it. This isn't a problem with your example but it might be worth considering in the future.catch(Exception e)is in over half of all SO questions, and putting that comment in each and every one of them strikes me as a really bad idea. Maybe not place drive-by irrelevant comments. Yes, 'catch everything and ignore it' is bad, but I don't think this is the way to fight that particular unfortunate aspect of java culture."notepad.exe"instead?ProcessBuilder, that time using the file extension to the executable, though with a similarly lazy/obfuscatory approach to exceptions