Handling Processes When They Terminate with the onExit Method

The Process.onExit and ProcessHandle.onExit methods return a CompletableFuture instance, which you can use to schedule tasks when a process terminates. Alternatively, if you want your application to wait for a process to terminate, then you can call onExit().get().

In the following excerpt, the method startProcessesTest creates three processes and then starts them. Afterward, it calls onExit().thenAccept(onExitMethod) on each of the processes; onExitMethod prints the process ID (PID), exit status, and output of the process.

public class ProcessTest {

  // ...

  static public void startProcessesTest() throws IOException, InterruptedException {
    List<ProcessBuilder> greps = new ArrayList<>();
    greps.add(new ProcessBuilder("/bin/sh", "-c", "grep -c \"java\" *"));
    greps.add(new ProcessBuilder("/bin/sh", "-c", "grep -c \"Process\" *"));
    greps.add(new ProcessBuilder("/bin/sh", "-c", "grep -c \"onExit\" *"));
    ProcessTest.startSeveralProcesses (greps, ProcessTest::printGrepResults);      
    System.out.println("\nPress enter to continue ...\n");
    System.in.read();  
  }

  static void startSeveralProcesses (
    List<ProcessBuilder> pBList,
    Consumer<Process> onExitMethod)
    throws InterruptedException {
    System.out.println("Number of processes: " + pBList.size());
    pBList.stream().forEach(
      pb -> {
        try {
          Process p = pb.start();
          System.out.printf("Start %d, %s%n",
            p.pid(), p.info().commandLine().orElse("<na>"));
          p.onExit().thenAccept(onExitMethod);
        } catch (IOException e) {
          System.err.println("Exception caught");
          e.printStackTrace();
        }
      }
    );
  }
  
  static void printGrepResults(Process p) {
    System.out.printf("Exit %d, status %d%n%s%n%n",
      p.pid(), p.exitValue(), output(p.getInputStream()));
  }

  private static String output(InputStream inputStream) {
    String s = "";
    try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) {
      s = br.lines().collect(Collectors.joining(System.getProperty("line.separator")));
    } catch (IOException e) {
      System.err.println("Caught IOException");
      e.printStackTrace();
    }
    return s;
  }

  // ...
}

The output of the method startProcessesTest is similar to the following. Note that the processes might exit in a different order than the order in which they were started.

Number of processes: 3
Start 12401, /bin/sh -c grep -c "java" *
Start 12403, /bin/sh -c grep -c "Process" *
Start 12404, /bin/sh -c grep -c "onExit" *

Press enter to continue ...

Exit 12401, status 0
ProcessTest.class:0
ProcessTest.java:16

Exit 12404, status 0
ProcessTest.class:0
ProcessTest.java:8

Exit 12403, status 0
ProcessTest.class:0
ProcessTest.java:38

This method calls the System.in.read() method to prevent the program from terminating before all the processes have exited (and have run the method specified by the thenAccept method).

If you want to wait for a process to terminate before proceeding with the rest of the program, then call onExit().get():

  static void startSeveralProcesses (
    List<ProcessBuilder> pBList, Consumer<Process> onExitMethod)
    throws InterruptedException {
    System.out.println("Number of processes: " + pBList.size());
    pBList.stream().forEach(
      pb -> {
        try {
          Process p = pb.start();
          System.out.printf("Start %d, %s%n",
            p.pid(), p.info().commandLine().orElse("<na>"));
          p.onExit().get();
          printGrepResults(p);          
        } catch (IOException|InterruptedException|ExecutionException e ) {
          System.err.println("Exception caught");
          e.printStackTrace();
        }
      }
    );
  }

The ComputableFuture class contains a variety of methods that you can call to schedule tasks when a process exits including the following:

  • thenApply: Similar to thenAccept, except that it takes a lambda expression of type Function (a lambda expression that returns a value).

  • thenRun: Takes a lambda expression of type Runnable (no formal parameters or return value).

  • thenApplyAsyc: Runs the specified Function with a thread from ForkJoinPool.commonPool().

Because ComputableFuture implements the Future interface, this class also contains synchronous methods:

  • get​(long timeout, TimeUnit unit): Waits, if necessary, at most the time specified by its arguments for the process to complete.

  • isDone: Returns true if the process is completed.