DEV Community

Sadiul Hakim
Sadiul Hakim

Posted on

Java Process API Basics

The Java Process API, available since Java 5 and significantly improved in Java 9, provides a standard way to interact with native operating system processes from within a Java application. It allows you to start, stop, monitor, and manage external processes, such as running a shell script, a Python program, or any other executable file.


1. What is it?

The Process API is a set of classes and interfaces in the java.lang and java.lang.ProcessHandle packages that allow a Java program to interact with and control processes running on the host operating system. It essentially acts as a bridge between the Java Virtual Machine (JVM) and external processes.


2. When to Use It and When Not?

When to Use It:

  • Executing external programs: You need to run an executable file, a shell script, or a program written in another language (e.g., Python, C++) from your Java application.
  • System administration tasks: Automating tasks like file zipping, running a database backup utility, or managing system services.
  • Creating a graphical user interface (GUI) for a command-line tool: Your Java GUI can launch and interact with a command-line tool, displaying its output and providing user input.
  • Parallel processing: You can offload a computationally intensive task to a separate process to avoid blocking the main Java thread.

When Not to Use It:

  • Purely internal tasks: If you can perform a task using standard Java libraries (e.g., java.nio.file for file operations or java.util.zip for zipping files), avoid using an external process. Relying on external executables makes your application less portable.
  • Inter-process communication (IPC) for complex data: For complex data exchange, consider using more robust IPC mechanisms like sockets, message queues, or shared memory, as the Process API's standard I/O streams are best for text-based or simple binary data.
  • Cross-platform applications where external tools aren't guaranteed: If your application needs to run on various operating systems and you can't guarantee that the external executable is available on all of them, try to find a pure Java solution.

3. Why Use It?

  • Portability: It offers a consistent, cross-platform way to manage external processes, abstracting away the operating system-specific commands.
  • Security: The API provides a controlled and secure way to interact with external programs, preventing a user from directly executing arbitrary system commands.
  • Robustness: It allows for better error handling and resource management, as you can monitor the process's exit code, check if it's still running, and destroy it if necessary.
  • Flexibility: You can redirect the standard I/O streams of the external process, allowing your Java program to read its output and provide it with input.

4. Available Classes and Interfaces

  • ProcessBuilder: A class used to create a Process instance. It's the recommended way to start a new process as it provides more flexibility and control over the process's environment, directory, and I/O.
  • Process: An abstract class representing an external process. It allows you to wait for the process to complete, get its exit code, and access its I/O streams.
  • ProcessHandle: Introduced in Java 9, this interface represents the native handle of a process. It provides information about the process, such as its process ID (PID), parent process, and start time, and allows you to destroy it.
  • ProcessHandle.Info: An interface providing a snapshot of the process's information at a specific point in time.

5. Useful Methods

ProcessBuilder Methods:

  • command(String... command): Sets the command and its arguments.
  • directory(File directory): Sets the working directory for the new process.
  • redirectErrorStream(boolean redirect): Merges the standard error and standard output streams.
  • start(): Starts the process and returns a Process object.

Process Methods:

  • waitFor(): Waits for the process to terminate.
  • exitValue(): Returns the exit code of the process (throws IllegalThreadStateException if the process is still running).
  • getInputStream(): Gets the input stream of the process (which corresponds to its standard output).
  • getOutputStream(): Gets the output stream of the process (which corresponds to its standard input).
  • getErrorStream(): Gets the error stream of the process (which corresponds to its standard error).
  • destroy(): Kills the process.

ProcessHandle Methods (Java 9+):

  • current(): Returns the ProcessHandle for the current JVM.
  • pid(): Returns the process ID.
  • isAlive(): Checks if the process is still running.
  • parent(): Returns an Optional of the parent ProcessHandle.
  • children(): Returns a Stream of the child ProcessHandles.
  • onExit(): Returns a CompletableFuture<ProcessHandle> that completes when the process exits.

6. Examples

Example 1: Basic Process Execution

This example shows how to run a simple command (e.g., ls on Unix/Linux or dir on Windows) and print its output.

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class BasicProcessExample {
    public static void main(String[] args) {
        try {
            // Create a ProcessBuilder instance with the command.
            // On Windows, use "cmd.exe", "/c", "dir". On Linux/macOS, use "ls", "-l".
            ProcessBuilder processBuilder = new ProcessBuilder("ls", "-l");

            // Start the process
            Process process = processBuilder.start();

            // Use a BufferedReader to read the output from the process's standard output stream
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));

            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line); // Print each line of the output
            }

            // Wait for the process to complete and get its exit code
            int exitCode = process.waitFor();
            System.out.println("\nExited with error code : " + exitCode);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Example 2: Redirecting Input, Output, and Error Streams

This example demonstrates how to provide input to a process and handle its output and error streams.

import java.io.*;
import java.util.concurrent.TimeUnit;

public class RedirectIOExample {
    public static void main(String[] args) {
        try {
            // Create a process that will read from stdin and write to stdout/stderr.
            // Let's use a simple shell command that reads input and echoes it.
            // On Linux/macOS, "cat" is a good choice. On Windows, maybe a simple bat script.
            ProcessBuilder processBuilder = new ProcessBuilder("bash", "-c", "read -p 'Enter your name: ' name; echo 'Hello, '\"$name\"; echo 'This is an error' >&2");

            // Start the process
            Process process = processBuilder.start();

            // Get the output and error streams for reading
            BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            BufferedReader stderrReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));

            // Get the input stream for writing
            BufferedWriter stdinWriter = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));

            // Read the initial prompt from stdout and print it
            String prompt = stdoutReader.readLine();
            System.out.println(prompt);

            // Write input to the process's stdin
            stdinWriter.write("Alice");
            stdinWriter.newLine(); // Add a newline to simulate pressing Enter
            stdinWriter.flush(); // Flush the stream to send the data

            // Wait for a short time to let the process respond
            if (!process.waitFor(5, TimeUnit.SECONDS)) {
                System.err.println("Process did not exit in time.");
                process.destroyForcibly();
            }

            // Read and print the rest of the output
            String outputLine;
            while ((outputLine = stdoutReader.readLine()) != null) {
                System.out.println("STDOUT: " + outputLine);
            }

            // Read and print the error output
            String errorLine;
            while ((errorLine = stderrReader.readLine()) != null) {
                System.err.println("STDERR: " + errorLine);
            }

            int exitCode = process.exitValue();
            System.out.println("\nProcess exited with code: " + exitCode);
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

More Important Topic: ProcessHandle and CompletableFuture (Java 9+)

Java 9 introduced the ProcessHandle interface and asynchronous handling via CompletableFuture, which offers a non-blocking and more modern way to manage processes. Instead of using process.waitFor() which blocks the current thread, you can use onExit() to perform an action once the process is complete.

Example 3: Non-Blocking Process Management with ProcessHandle

import java.io.IOException;
import java.util.concurrent.CompletableFuture;

public class ProcessHandleExample {
    public static void main(String[] args) {
        try {
            // Create a ProcessBuilder for a long-running process
            ProcessBuilder pb = new ProcessBuilder("sleep", "5"); // 'sleep 5' waits for 5 seconds

            // Start the process
            Process process = pb.start();
            ProcessHandle handle = process.toHandle(); // Get the ProcessHandle

            System.out.println("Process with PID " + handle.pid() + " started.");

            // Use onExit() to get a CompletableFuture that completes when the process exits
            CompletableFuture<ProcessHandle> onExitFuture = handle.onExit();

            // Attach a callback to the CompletableFuture
            onExitFuture.thenAccept(ph -> {
                System.out.println("\nProcess with PID " + ph.pid() + " has exited.");
                System.out.println("Exit code: " + process.exitValue());
            });

            // The main thread can continue doing other work without being blocked
            System.out.println("Main thread is not blocked and can do other things...");

            // To ensure the JVM doesn't exit before the process, you could block on the future.
            // In a real application, a background thread would handle this.
            // For this example, we'll block the main thread to show the callback in action.
            onExitFuture.join();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)