1. Introduction

In this tutorial, we’re going to take an in-depth look at the Process API.

For a shallower look into how to use Process to execute a shell command, we can refer to our previous tutorial here.

The process that it refers to is an executing application. The Process class provides methods for interacting with these processes including extracting output, performing input, monitoring the lifecycle, checking the exit status, and destroying (killing) it.

2. Using Process Class for Compiling and Running Java Program

First, let’s see an example to compile and run another Java program with the help of Process API:

@Test
public void whenExecutedFromAnotherProgram_thenSourceProgramOutput3() throws IOException {
 
    Process process = Runtime.getRuntime()
      .exec("javac -cp src src\\main\\java\\com\\baeldung\\java9\\process\\OutputStreamExample.java");
    process = Runtime.getRuntime() 
      .exec("java -cp src/main/java com.baeldung.java9.process.OutputStreamExample");
    BufferedReader output = new BufferedReader(new InputStreamReader(process.getInputStream()));
    int value = Integer.parseInt(output.readLine());
 
    assertEquals(3, value);
}

Thus, the applications of executing Java code within an existing Java code are virtually limitless.

3. Creating Process

In general, our Java application can call upon any application which is running within our computer system subjective to Operating System restrictions.

Therefore we can execute applications. So, let’s see what the different use cases we can run by utilizing the Process API are.

In short, the ProcessBuilder class allows us to create subprocesses within our application.

Let’s see a demo of opening a Windows-based Notepad application:

ProcessBuilder builder = new ProcessBuilder("notepad.exe");
Process process = builder.start();

4. Destroying Process

The Process class also provides us with methods to destroy sub-processes or processes. However, how the application is killed is platform-dependent.

Next, let’s exemplify different use cases using practical examples.

4.1. Destroying a Process by Reference

Let’s say we’re using Windows OS and want to spawn the Notepad application and destroy it.

As before, we can create an instance of the Notepad application by using the ProcessBuilder class and the start() method. Then, we can call the destroy() method on our Process object.

4.2. Destroying a Process by ID

Typically, we can kill the running processes within our operating system that our application might not create. Be cautious while doing this, as unknowingly destroying a critical process can destabilize the operating system.

Firstly, we need to find out the process ID of the currently running process by checking the task manager and finding out the pid.

So, let’s see an example:

long pid = /* PID to kill */;
Optional<ProcessHandle> optionalProcessHandle = ProcessHandle.of(pid);
optionalProcessHandle.ifPresent(processHandle -> processHandle.destroy());

4.3. Destroying a Process by Force

When we execute the destroy() method, it kills the subprocess, as we saw earlier in the article. *In the case when destroy() doesn’t work, we have the option of destroyForcibly().*

Notably, we should always start with destroy() method first. Subsequently, we can perform a quick check on the sub-process using the isAlive() method.

If it returns true then execute destroyForcibly():

ProcessBuilder builder = new ProcessBuilder("notepad.exe");
Process process = builder.start();
process.destroy();
if (process.isAlive()) {
    process.destroyForcibly();
}

5. Waiting for a Process to Complete

We also have two overloaded methods, through which we can ensure we can wait for a process to be completed.

5.1. waitfor()

In short, when this method executes, it places the current execution process thread in a blocking-wait state until the sub-process terminates.

So, let’s see it in action:

ProcessBuilder builder = new ProcessBuilder("notepad.exe");
Process process = builder.start();
assertThat(process.waitFor() >= 0);

As we can see, the current thread waits for the subprocess thread to end. Once the subprocess ends, the current thread will continue its execution.

5.2. waitfor(long timeOut, TimeUnit time)

Typically, executing this method will place the current execution process thread in a blocking-wait state until the sub-process either terminates or times out.

So, let’s see this in practice:

ProcessBuilder builder = new ProcessBuilder("notepad.exe");
Process process = builder.start();
assertFalse(process.waitFor(1, TimeUnit.SECONDS));

We can see from the above example for the current thread to continue execution it will keep on waiting for the subprocess thread to end or if the specified time interval has elapsed.

When this method is executed, then it will return a boolean value of true if the subprocess has exited or a boolean value of false if the wait time had elapsed before the subprocess exited.

6. exitValue()

When this method is run then the current thread won’t wait for the sub-process to get terminated or destroyed, however, it will throw an IllegalThreadStateException if the subprocess isn’t terminated.

Another way around if the subprocess has been successfully terminated then it will result in an exit value of the process. It can be any possible positive integer number.

So, let’s look at an example when the exitValue() method returns a positive integer when the subprocess has been terminated successfully:

@Test
public void 
  givenSubProcess_whenCurrentThreadWillNotWaitIndefinitelyforSubProcessToEnd_thenProcessExitValueReturnsGrt0() 
  throws IOException {
    ProcessBuilder builder = new ProcessBuilder("notepad.exe");
    Process process = builder.start();
    assertThat(process.exitValue() >= 0);
}

7. isAlive()

Typically, when we’d like to perform business processing which is subjective whether the process is alive or not, we can perform a quick check to find whether the process is alive or not which returns a boolean value.

Let’s see a quick example of it:

ProcessBuilder builder = new ProcessBuilder("notepad.exe");
Process process = builder.start();
Thread.sleep(10000);
process.destroy();
assertTrue(process.isAlive());

8. Handling Process Streams

By default, the created subprocess does not have its terminal or console. All its standard I/O (i.e., stdin, stdout, stderr) operations will be sent to the parent process. Thereby the parent process can use these streams to feed input to and get output from the subprocess.

Consequently, this gives us a huge amount of flexibility as it gives us control over the input/output of our sub-process.

8.1. getErrorStream()

Interestingly we can fetch the errors generated from the subprocess and thereon perform business processing.

After that, we can execute specific business processing checks based on our requirements.

Let’s see an example:

@Test
public void givenSubProcess_whenEncounterError_thenErrorStreamNotNull() throws IOException {
    Process process = Runtime.getRuntime().exec(
      "javac -cp src src\\main\\java\\com\\baeldung\\java9\\process\\ProcessCompilationError.java");
    BufferedReader error = new BufferedReader(new InputStreamReader(process.getErrorStream()));
    String errorString = error.readLine();
    assertNotNull(errorString);
}

8.2. getInputStream()

We can also fetch the output generated by a subprocess and consume it within the parent process thus allowing share information between the processes:

@Test
public void givenSourceProgram_whenReadingInputStream_thenFirstLineEquals3() throws IOException {
    Process process = Runtime.getRuntime().exec(
      "javac -cp src src\\main\\java\\com\\baeldung\\java9\\process\\OutputStreamExample.java");
    process = Runtime.getRuntime()
      .exec("java -cp  src/main/java com.baeldung.java9.process.OutputStreamExample");
    BufferedReader output = new BufferedReader(new InputStreamReader(process.getInputStream()));
    int value = Integer.parseInt(output.readLine());
 
    assertEquals(3, value);
}

8.3. getOutputStream()

We can send input to a subprocess from a parent process:

Writer w = new OutputStreamWriter(process.getOutputStream(), "UTF-8");
w.write("send to child\n");

8.4. Filter Process Streams

It’s a perfectly valid use case to interact with selective running processes.

The process class provides us the facility to selectively filter running processes based on a certain predicate.

After that, we can perform business operations on this selective process set:

@Test
public void givenRunningProcesses_whenFilterOnProcessIdRange_thenGetSelectedProcessPid() {
    assertThat(((int) ProcessHandle.allProcesses()
      .filter(ph -> (ph.pid() > 10000 && ph.pid() < 50000))
      .count()) > 0);
}

9. Java 9 Improvements

Java 9 introduced new options and methods for getting information about current and spawned processes. Let’s dive deeper and explore each feature in detail.

9.1. Current Java Process Information

We can now obtain a lot of information about the process via the API java.lang.ProcessHandle.Info API:

  • the command used to start the process
  • the arguments of the command
  • time instant when the process is started
  • total time spent by it and the user who created it

For example, here’s how we can do that:

private static void infoOfCurrentProcess() {
    ProcessHandle processHandle = ProcessHandle.current();
    ProcessHandle.Info processInfo = processHandle.info();

    log.info("PID: " + processHandle.pid());
    log.info("Arguments: " + processInfo.arguments());
    log.info("Command: " + processInfo.command());
    log.info("Instant: " + processInfo.startInstant());
    log.info("Total CPU duration: " + processInfo.totalCpuDuration());
    log.info("User: " + processInfo.user());
}

It’s important to note that java.lang.ProcessHandle.Info is a public interface defined within another interface, java.lang.ProcessHandle. The JDK provider (Oracle JDK, Open JDK, Zulu, or others) should implement these interfaces in a way that returns the relevant information for the processes.

The output depends on the operating system and Java version. Here’s an example of what the output can look like:

16:31:24.784 [main] INFO  c.b.j.process.ProcessAPIEnhancements - PID: 22640
16:31:24.790 [main] INFO  c.b.j.process.ProcessAPIEnhancements - Arguments: Optional[[Ljava.lang.String;@2a17b7b6]
16:31:24.791 [main] INFO  c.b.j.process.ProcessAPIEnhancements - Command: Optional[/Library/Java/JavaVirtualMachines/jdk-13.0.1.jdk/Contents/Home/bin/java]
16:31:24.795 [main] INFO  c.b.j.process.ProcessAPIEnhancements - Instant: Optional[2021-08-31T14:31:23.870Z]
16:31:24.795 [main] INFO  c.b.j.process.ProcessAPIEnhancements - Total CPU duration: Optional[PT0.818115S]
16:31:24.796 [main] INFO  c.b.j.process.ProcessAPIEnhancements - User: Optional[username]

9.2. Spawned Process Information

It’s also possible to get the process information of a newly spawned process. In this case, after we spawn the process and get an instance of the java.lang.Process, we invoke the toHandle() method on it to get an instance of java.lang.ProcessHandle.

The rest of the details remain the same as in the section above:

String javaCmd = ProcessUtils.getJavaCmd().getAbsolutePath();
ProcessBuilder processBuilder = new ProcessBuilder(javaCmd, "-version");
Process process = processBuilder.inheritIO().start();
ProcessHandle processHandle = process.toHandle();

9.3. Enumerating Live Processes in the System

We can list all the processes currently in the system that are visible to the current process. The returned list is a snapshot of the API invoked, so it’s possible that some processes terminated after taking the snapshot or that new processes were added.

In order to do that, we can use the static method allProcesses() available in the java.lang.ProcessHandle interface which returns us a Stream of ProcessHandle:

private static void infoOfLiveProcesses() {
    Stream<ProcessHandle> liveProcesses = ProcessHandle.allProcesses();
    liveProcesses.filter(ProcessHandle::isAlive)
        .forEach(ph -> {
            log.info("PID: " + ph.pid());
            log.info("Instance: " + ph.info().startInstant());
            log.info("User: " + ph.info().user());
        });
}

9.4. Enumerating Child Processes

There are two variants to do this:

  • get direct children of the current process
  • get all the descendants of the current process

The former is achieved by using the method children() and the latter is achieved by using the method descendants():

private static void infoOfChildProcess() throws IOException {
    int childProcessCount = 5;
    for (int i = 0; i < childProcessCount; i++) {
        String javaCmd = ProcessUtils.getJavaCmd()
          .getAbsolutePath();
        ProcessBuilder processBuilder
          = new ProcessBuilder(javaCmd, "-version");
        processBuilder.inheritIO().start();
    }

    Stream<ProcessHandle> children = ProcessHandle.current()
      .children();
    children.filter(ProcessHandle::isAlive)
      .forEach(ph -> log.info("PID: {}, Cmd: {}", ph.pid(), ph.info()
        .command()));
    Stream<ProcessHandle> descendants = ProcessHandle.current()
      .descendants();
    descendants.filter(ProcessHandle::isAlive)
      .forEach(ph -> log.info("PID: {}, Cmd: {}", ph.pid(), ph.info()
        .command()));
}

9.5. Triggering Dependent Actions on Process Termination

We might want to run something on the termination of the process. This can be achieved by using the onExit() method in the java.lang.ProcessHandle interface. The method returns us a CompletableFuture which provides the ability to trigger dependent operations when the CompletableFuture is completed.

Here, the CompletableFuture indicates the process has completed, but it doesn’t matter if the process has completed successfully or not. We invoke the get() method on the CompletableFuture, to wait for its completion:

private static void infoOfExitCallback() throws IOException, InterruptedException, ExecutionException {
    String javaCmd = ProcessUtils.getJavaCmd()
      .getAbsolutePath();
    ProcessBuilder processBuilder
      = new ProcessBuilder(javaCmd, "-version");
    Process process = processBuilder.inheritIO()
      .start();
    ProcessHandle processHandle = process.toHandle();

    log.info("PID: {} has started", processHandle.pid());
    CompletableFuture onProcessExit = processHandle.onExit();
    onProcessExit.get();
    log.info("Alive: " + processHandle.isAlive());
    onProcessExit.thenAccept(ph -> {
        log.info("PID: {} has stopped", ph.pid());
    });
}

The onExit() method is available in the java.lang.Process interface as well.

10. Conclusion

In this article, we covered most of the important features of the Process API in Java. Along the way, we discussed the new improvements introduced in Java 9.

As always, the source code of the article is available over on Github.