6 Process API

The Process API lets you start, retrieve information about, and manage native operating system processes.

With this API, you can work with operating system processes as follows:

  • Run arbitrary commands:

    • Filter running processes.

    • Redirect output.

    • Connect heterogeneous commands and shells by scheduling processes to start when another ends.

  • Test the execution of commands:

    • Run a series of tests.

    • Log output.

    • Cleanup leftover processes.

  • Monitor commands:

    • Monitor long-running processes and restart them if they terminate

    • Collect usage statistics

Process API Classes and Interfaces

The Process API consists of the classes and interfaces ProcessBuilder, Process, ProcessHandle, and ProcessHandle.Info.

ProcessBuilder Class

The ProcessBuilder class lets you create and start operating system processes.

See Creating a Process for examples on how to create and start a process. The ProcessBuilder class manages various process attributes, which the following table summarizes:

Table 6-1 ProcessBuilder Class Attributes and Related Methods

Process Attribute Description Related Methods
Command Strings that specify the external program file to call and its arguments, if any.
Environment The environment variables (and their values). This is initially a copy of the system environment of the current process.
Working directory By default, the current working directory of the current process.
Standard input source By default, a process reads standard input from a pipe; access this through the output stream returned by the Process.getOutputStream method.
Standard output and standard error destinations By default, a process writes standard output and standard error to pipes; access these through the input streams returned by the Process.getInputStream and Process.getErrorStream methods. See Redirecting Output from a Process for an example.
redirectErrorStream property Specifies whether to send standard output and error output as two separate streams (with a value of false) or merge any error output with standard output (with a value of true).

Process Class

The methods in the Process class let you to control processes started by the methods ProcessBuilder.start and Runtime.exec. The following table summarizes these methods:

The following table summarizes the methods of the Process class.

Table 6-2 Process Class Methods

Method Type Related Methods
Wait for the process to complete.
Retrieve information about the process.
Retrieve input, output, and error streams. See Handling Processes When They Terminate with the onExit Method for an example.
Retrieve direct and indirect child processes.
Destroy or terminate the process.
Return a CompletableFuture instance that will be completed when the process exits. See Handling Processes When They Terminate with the onExit Method for an example.

ProcessHandle Interface

The ProcessHandle interface lets you identify and control native processes. The Process class is different from ProcessHandle because it lets you control processes started only by the methods ProcessBuilder.start and Runtime.exec; however, the Process class lets you access process input, output, and error streams.

See Filtering Processes with Streams for an example of the ProcessHandle interface. The following table summarizes the methods of this interface:

Table 6-3 ProcessHandle Interface Methods

Method Type Related Methods
Retrieve all operating system processes.
Retrieve process handles.
Retrieve information about the process.
Retrieve streams of direct and indirect child processes.
Destroy processes.
Return a CompletableFuture instance that will be completed when the process exits. See Handling Processes When They Terminate with the onExit Method for an example.

ProcessHandle.Info Interface

The ProcessHandle.Info interface lets you retrieve information about a process, including processes created by the ProcessBuilder.start method and native processes.

See Getting Information About a Process for an example of the ProcessHandle.Info interface. The following table summarizes the methods in this interface:

Table 6-4 ProcessHandle.Info Interface Methods

Method Description
arguments() Returns the arguments of the process as a String array.
command() Returns the executable path name of the process.
commandLine() Returns the command line of the process.
startInstant() Returns the start time of the process.
totalCpuDuration() Returns the total CPU time accumulated of the process.
user() Returns the user of the process.

Creating a Process

To create a process, first specify the attributes of the process, such as the command name and its arguments, with the ProcessBuilder class. Then, start the process with the ProcessBuilder.start method, which returns a Process instance.

The following lines create and start a process:

  ProcessBuilder pb = new ProcessBuilder("echo", "Hello World!");
  Process p = pb.start();

In the following excerpt, the setEnvTest method sets two environment variables, horse and oats, then prints the value of these environment variables (as well as the system environment variable HOME) with the echo command:

  public static void setEnvTest() throws IOException, InterruptedException {
    ProcessBuilder pb =
      new ProcessBuilder("/bin/sh", "-c", "echo $horse $dog $HOME").inheritIO();
    pb.environment().put("horse", "oats");
    pb.environment().put("dog", "treats");
    pb.start().waitFor();
  }

This method prints the following (assuming that your home directory is /home/admin):

oats treats /home/admin

Getting Information About a Process

The method Process.pid returns the native process ID of the process. The method Process.info returns a ProcessHandle.Info instance, which contains additional information about the process, such as its executable path name, start time, and user.

In the following excerpt, the method getInfoTest starts a process and then prints information about it:

  public static void getInfoTest() throws IOException {
    ProcessBuilder pb = new ProcessBuilder("echo", "Hello World!");
    String na = "<not available>";
    Process p = pb.start();
    ProcessHandle.Info info = p.info();
    System.out.printf("Process ID: %s%n", p.pid());
    System.out.printf("Command name: %s%n", info.command().orElse(na));
    System.out.printf("Command line: %s%n", info.commandLine().orElse(na));
    
    System.out.printf("Start time: %s%n",
      info.startInstant().map(i -> i.atZone(ZoneId.systemDefault())
                                    .toLocalDateTime().toString())
                         .orElse(na));
    
    System.out.printf("Arguments: %s%n",
      info.arguments().map(a -> Stream.of(a)
                                      .collect(Collectors.joining(" ")))
                      .orElse(na));
  
    System.out.printf("User: %s%n", info.user().orElse(na));
  }

This method prints output similar to the following:

Process ID: 18761
Command name: /usr/bin/echo
Command line: echo Hello World!
Start time: 2017-05-30T18:52:15.577
Arguments: <not available>
User: administrator

Note:

  • The attributes of a process vary by operating system and are not available in all implementations. In addition, information about processes is limited by the operating system privileges of the process making the request.

  • All the methods in the interface ProcessHandle.Info return instances of Optional<T>; always check if the returned value is empty.

Redirecting Output from a Process

By default, a process writes standard output and standard error to pipes. In your application, you can access these pipes through the input streams returned by the methods Process.getOutputStream and Process.getErrorStream. However, before starting the process, you can redirect standard output and standard error to other destinations, such as a file, with the methods redirectOutput and redirectError.

In the following excerpt, the method redirectToFileTest redirects standard input to a file, out.tmp, then prints this file:

  public static void redirectToFileTest() throws IOException, InterruptedException {
    File outFile = new File("out.tmp");
    Process p = new ProcessBuilder("ls", "-la")
      .redirectOutput(outFile)
      .redirectError(Redirect.INHERIT)
      .start();
    int status = p.waitFor();
    if (status == 0) {
      p = new ProcessBuilder("cat" , outFile.toString())
        .inheritIO()
        .start();
      p.waitFor();
    }
  }

The excerpt redirects standard output to the file out.tmp. It redirects standard error to the standard error of the invoking process; the value Redirect.INHERIT specifies that the subprocess I/O source or destination is the same as that of the current process. The call to the inheritIO() method is equivalent to redirectInput(Redirect.INHERIT).redirectOuput(Redirect.INHERIT).redirectError(Redirect.INHERIT).

Filtering Processes with Streams

The method ProcessHandle.allProcesses returns a stream of all processes visible to the current process. You can filter the ProcessHandle instances of this stream the same way that you filter elements from a collection.

In the following excerpt, the method filterProcessesTest prints information about all the processes owned by the current user, sorted by the process ID of their parent's process:

public class ProcessTest {

  // ...

  static void filterProcessesTest() {
    Optional<String> currUser = ProcessHandle.current().info().user();
    ProcessHandle.allProcesses()
        .filter(p1 -> p1.info().user().equals(currUser))
        .sorted(ProcessTest::parentComparator)
        .forEach(ProcessTest::showProcess);
  }

  static int parentComparator(ProcessHandle p1, ProcessHandle p2) {
    long pid1 = p1.parent().map(ph -> ph.pid()).orElse(-1L);
    long pid2 = p2.parent().map(ph -> ph.pid()).orElse(-1L);
    return Long.compare(pid1, pid2);
  }

  static void showProcess(ProcessHandle ph) {
    ProcessHandle.Info info = ph.info();
    System.out.printf("pid: %d, user: %s, cmd: %s%n",
      ph.pid(), info.user().orElse("none"), info.command().orElse("none"));
  } 

  // ...
}

Note that the allProcesses method is limited by native operating system access controls. Also, because all processes are created and terminated asynchronously, there is no guarantee that a process in the stream is alive or that no other processes may have been created since the call to the allProcesses method.

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.

Controlling Access to Sensitive Process Information

Process information may contain sensitive information such as user IDs, paths, and arguments to commands. Control access to process information with a security manager.

When running as a normal application, a ProcessHandle has the same operating system privileges to information about other processes as a native application; however, information about system processes may not be available.

If your application uses the SecurityManager class to implement a security policy, then to enable it to access process information, the security policy must grant RuntimePermission("manageProcess"). This permission enables native process termination and access to the process ProcessHandle information. Note that this permission enables code to identify and terminate processes that it did not create.