javawindowsprocessruntimeexec

Java Process.waitFor() does not wait


I am following a book on Java 'Java: The Complete Reference, 11th Edition" which has the following code

class ExecDemoFini {
    public static void main(String args[]) {
        Runtime r = Runtime.getRuntime();
        Process p = null;

        try {
            p = r.exec("notepad");
            p.waitFor();
        } catch(Exception e) {
            System.out.println("Error executing notepad.");
        }
        System.out.println("Notepad returned " + p.exitValue());
    }
}

According to the book, this program, when run in a Windows environment, is supposed to wait for notepad to terminate. But it does not. It returns an exit code of 0 immediately. I read on another post that had a similar question, that maybe the process that is starting (in this case, notepad) it creates another subprocess and then terminates itself. Is that the case here? Is that how notepad works? If not, I want to know why this example does not work in my computer. All the other examples have been accurate so far.

I use Windows 11 and openJDK version 11.0.0.2 .


Solution

  • 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