PSP Logo

2.2 Process management in Java - ProcessBuilder and Process

IES Doctor Balmis Logo

PSP class notes by Vicente Martínez is licensed under CC BY-NC-SA 4.0

2.2 Process management in Java ProcessBuilder and Process

2.2.1 Preparation and setting of a process

The class to set the running attributes for a new process, before it is being run, is the ProcessBuilder class.

Specification java.lang.ProcessBuilderOpen in new window

This is an auxiliary class for the Process and is instantiated to manage a collection of process attributes. We can invoke the start method to create a new process with the attributes defined by the instance of the ProcessBuilder class.

Repeated calls to the start method would create a new process with the same attributes.

The ProcessBuilder class defines two constructors, such as:

ProcessBuilder(List<String> command)
ProcessBuilder(String... command)

The meaning implied by the parameters passed to both constructors is same. In the first constructor, the command to be executed, along with command line arguments, is passed in a list of strings. And, in the second constructor, the command and the command line arguments are specified through the varargs parameter. We can use either of the constructors, depending upon the way to pass the parameter.

Arguments vs Parameters

If we want to launch a command with parameters, the command cannot be sent to ProcessBuilder in raw mode, it must be processed and converted into a List in order to make it work.

// Different modes to pass the command to ProcessBuilder constructors
// 1st mode: using a string. It fails with parameters, 
// Only works with commands having arguments
String command1 = "notepad.exe prueba1.txt"
ProcessBuilder pb = new ProcessBuilder(command1);

// 2nd mode: using an array of strings. It also works with parameters
String[] command2 = {"cmd", "/c", "dir", "/o"};
ProcessBuilder pb = new ProcessBuilder(command2);

// 3rd mode: using a string and splitting it to convert into a List
String command3 =  "c:/windows/system32/shutdown -s -t 0";  
// Regular expresion \s means splitting the string by blank spaces
ProcessBuilder pb = new ProcessBuilder(command3.split("\\s"));

OS shutdown

You can use shutdown -s command to shutdown system. For windows OS, you need to provide full path of shutdown command e.g. C:\Windows\System32\shutdown.

Here you can use -s switch to shutdown system, -r switch to restart system, -h to put the system into hibernation, and -t switch to specify time delay.

Windows shutdown referenceOpen in new window

Activity psp.activities.U2A1_Shutdowner

Create a new Java application project (package psp.activities & main class U2A1_Shutdowner) Using the command line, ask the user for the action he wants to do with the computer (shutdown ,restart or suspend) and how much time he needs before shutting down the system.

Find information about the shutdown command in GNU/Linux and make your app work in both systems.

Your app has to prepare the right command for the answers the user has given and for the OS it is running on.

Get the ProcessBuilder.command() result and show it on the console in a readable format.

U2A1_Shutdowner solution
public class U2A1_Shutdowner {

    public static void main(String[] args) throws IOException {
        // Ask for the required information to prepare the command
        Scanner keyboard = new Scanner(System.in);
        
        System.out.print("Select your option (s-shutdown / r-reboot / h-hibernate): ");
        String shutdownOption = keyboard.nextLine();        
        
        System.out.print("How much seconds will the command wait to be run? (0 means immediately): ");
        String shutdownTime = keyboard.nextLine();        
        
        // Prepare the command
        String command;
        if (System.getProperty("os.name").toLowerCase().startsWith("windows")) {
            command = "C:/Windows/System32/shutdown -" + shutdownOption + " -t " + shutdownTime;
        } else {
            command = "shutdown -" + shutdownOption + " -t " + shutdownTime;
        }
        
        // Prepare the process and launch it
        ProcessBuilder shutdowner = new ProcessBuilder(command.split("\\s"));
        //shutdowner.start();
        
        // Show the command to be run
        System.out.print("El comando a ejecutar es:  ");
        for (String commandPart: shutdowner.command()) {
            System.out.print(commandPart + " ");
        }
        System.out.println("");
    }    
}

Setting the command at runtime

If we want to set the command to be run at runtime, or at the time the ProcessBuilder instance is created we still don't know the command, it can be set later by using the command(String).

The same way as the constructors, we have two versions of command method

    command(List<String> command)
    command(String... command)

and there's also another command method, without parameters, to retrieve the command and parameters already set for the ProcessBuilder instance. Once we have the parameters list, we can modify it using List methods.

// Sets and modifies the command after ProcessBuilder object is created
String command = "java -jar install.jar -install"; // tmp dir is missing
ProcessBuilder pbuilder = new ProcessBuilder(command.split("\\s"));
if (isWindows) {
    pbuilder.command().add(0, "cmd"); // Sets the 1st element
    pbuilder.command().add(1, "/c"); // Sets the 2nd element
    pbuilder.command().add("c:/temp"); // Sets the last element
    // Command to run cmd /c java -jar install.jar -install c:/temp
} else {
    pbuilder.command().add(0, "sh"); // Sets the 1st element
    pbuilder.command().add(1, "-c"); // Sets the 2nd element
    pbuilder.command().add("/tmp"); // Sets the last element
    // Command to run: sh -c java -jar install.jar -install /tmp
}

// Starts the process
pbuilder.start();

Additional settings for a process

Some of the settings that can be changed for a process are:

  • Set the working directory where the process will be run We can override the default working directory of the current process by calling the directory method and passing a File object. By default, the current working directory is set to the value returned by the user.dir system property.

    // Change working directory for the running process
    pbuilder.directory(new File(System.getProperty("user.home")));
    
  • Set-up a custom key-value map and modify an existing one using builder.environment()

    // Retrieve and modify the process environment
    Map<String, String> environment = pbuilder.environment();
    // Get the PATH environment variable and add a new directory
    String systemPath = environment.get("path") + ";c:/users/public";
    environment.replace("path", systemPath);
    // Add a new environment variable and use it as a part of the command
    environment.put("GREETING", "Hola Mundo");
    processBuilder.command("/bin/bash", "-c", "echo $GREETING");
    

    Environment variables vs System properties

    With Runtime we also accessed System properties, that are different from this environment ones.

  • Redirect input and output streams to custom replacements

  • Inherit both of them to the streams of the current JVM process using builder.inheritIO()

    This two settings will be covered later in this unit.

Activity psp.activities.U2A2_WorkingDirectory

Create a new Java application project (package psp.activities & main class U2A2_WorkingDirectory ) Prepare a process to run the dir/ls command to check that the directory listing is for the directory pointed by the user.dir property. In the same application, change the value for the user.dir property. Finally, set a working directory for the process.

Print the user.dir environment value for the three scenarios after being changed.

U2A2_WorkingDirectory solution
public class U2A2_WorkingDirectory {

    public static void main(String[] args) throws IOException, InterruptedException {
        // Prepare the command
        String command;
        if (System.getProperty("os.name").toLowerCase().startsWith("windows")) {
            command = "cmd /c dir";
        } else {
            command = "sh -c ls";
        }
               
        //1st - Default working directory
        
        // Prepare the process 
        ProcessBuilder commander = new ProcessBuilder(command.split("\\s"));       
        commander.inheritIO();
        
        // Show Process and System properties
        System.out.println("Working directory: " + commander.directory());
        System.out.println("user.dir variable: " + System.getProperty("user.dir"));
        
        // Launch the process and show its result
        // Working directory is null but the process is run on the current dir
        commander.start().waitFor();
        
        
        //2nd - Change user.dir but not the working directory
        
        // Change the user.dir system property
        System.setProperty("user.dir", System.getProperty("user.home"));
        
        // Prepare the process
        commander = new ProcessBuilder(command.split("\\s"));       
        commander.inheritIO();
        
        // Show Process and System properties
        System.out.println("Working directory: " + commander.directory());
        System.out.println("user.dir variable: " + System.getProperty("user.dir"));
        
        // Launch the process and show its result
        // Working directory is null but the process is run on the current dir        
        commander.start().waitFor();
        
        
        // 3rd - Change the working directory
        
        // Prepare the process
        commander = new ProcessBuilder(command.split("\\s"));       
        commander.inheritIO();
        
        // Show Process and System properties
        commander.directory(new File(System.getProperty("user.home")));             
        System.out.println("Working directory: " + commander.directory());
        System.out.println("user.dir variable: " + System.getProperty("user.dir"));

        // Launch the process and show its result
        // Working directory is user.home and the process is run on it
        commander.start().waitFor();        
    }
}

2.2.2 Process control from parent

The Process is an abstract class defined in the java.lang package that encapsulates the runtime information of a program in execution. The start method invoked by the ProcessBuilder class returns a reference to this class instance. There is an another way to create an instance of this class, through the exec method of the Runtime instance.

The methods defined by the Process class can be used to perform input/output operations from the process, check the exit status of the process, wait for it to complete, and terminate the process. These methods, however, are not built to work on special processes of the native platform like daemon processes, shell scripts, and so on.

Specification java.lang.ProcessOpen in new window

Input/ output from the child process

Intriguingly, the process created by the start() method does not own a console. Instead, it redirects (stdin, stdout, stderr) to the parent process. If need be, we can access them via streams obtained using methods defined in the class, such as getInputStream(), getOutputStream() and getErrorSteam(). These are the ways we can feed input to and get results from the sub processes.

Some of the common methods defined in this class are:

methodDescription
int exitValue()Exit code returned from the process executed
Boolean isAlive()Checks if the invoking process is still running.
int waitFor()Parent process waits for the child process to end. The integer value returned by the method is the exit code by the process.
Boolean waitFor(long timeOut, TimeUnit unit)Overloaded method of previous one. We can specify the wait time. This method returns true if the process has terminated and false if timeout has occurred.
void destroy()These two methods are used to kill or terminate the process. One, the second, just does it forcibly.
Process destroyForcibly()

Let’s write a simple Java program to open an application as a separate process. After it is opened, the program would wait for, say, 10 seconds and then destroy the process, which will immediately close the application.

public class ProcessDemo {

   public static void main(String[] args) throws Exception {

      ProcessBuilder pb = new ProcessBuilder("C:/Program Files (x86)/Notepad++/notepad++.exe");
      // Effectively launch the process
      Process p = pb.start();
      // Check is process is alive or not
      boolean alive = p.isAlive();
      // Wait for the process to end for 10 seconds.
      if (p.waitFor(10, TimeUnit.SECONDS)) {
          System.out.println("Process has finished");
      } else {
          System.out.println("Timeout. Process hasn't finished");
      }
      // Force process termination.
      p.destroy();
      // Check again if process remains alive
      alive = p.isAlive();
      // Get the process exit value
      int status = p.exitValue();        
   }
}

Exit codes

An exit code, or sometimes known as a return code, is the code returned to a parent process by an executable. The standard exit code is 0 for success and any number from 1 to 255 for anything else.

Activity psp.activities.U2A3_ExitValue

Create a new Java application project (package psp.activities & main class U2A3_ExitValue) Prepare a process to run different commands (notepad, calc, shell commands) one after each other, and make your application get their exit code. Print it.

Commands can be hardcoded. As an optional improvement for this activity you can ask the user for the command and make your app interactive. There must be an option to exit the app (empty command for instance).

Try with non-existing applications or using wrong arguments/parameters for commands.

Can you force a process not to be successful?

How can you know your own process exit code?

U2A3_ExitValue solution
public class U2A3_ExitValue {

    public static void main(String[] args) {
        do {
            // Código para pedir un programa/comando a ejecutar
            Scanner teclado = new Scanner(System.in);
            System.out.println("Introduce el programa / comando que quieres ejecutar (intro para acabar): ");
            String comando = teclado.nextLine();
            
            if (comando.equals("")) System.exit(0);

            try {
                // Preparamos el entrono de ejecución del proceso
                // Como no sabemos el contenido del comando, forzamos su conversión
                // a una lista para que no haya problemas con su ejecución
                ProcessBuilder pb = new ProcessBuilder(comando.split("\\s"));

                // Lanzamos el proceso hijo
                Process p = pb.start();          

                // Esperamos a que acabe para recoger el valor de salida
                int exitValue = p.waitFor();

                if (exitValue == 0) {
                    System.out.println("El comando " + pb.command().toString() + " ha finalizado bien");
                } else {
                    System.out.println("El comando " + pb.command().toString() + " ha finalizado con errores. Código (" + exitValue + ")");
                }

            } catch (InterruptedException | IOException ex) {
                System.err.println(ex.getLocalizedMessage());
                ex.printStackTrace();
            }                       
        } while (true);
    }
}

Exceptions management

Call to method waitFor implies that the parent process gets locked until child process ends, or until a signal from the system (Exception) is received.

It's better to handle exceptions than to throw them to upper levels.

2.2.3 Spawn a java application from a class into the same project

For some activities you'll be required to create the parent and the child processes as Java applications. Then, from one the classes you will need to launch the other one.

This implies that both classes are gonna have a main method. So, in the project properties we'll need to set which is the main class that will be run first, usually the Launcher class (parent process).

Main class

Before one class can run the other, at least the second one (child process) must be compiled, that is, the .class file has to be generated into the build/classes directory.

Working directory

Them and only then we can set the process environment to spawn a new process from an existing class. Here is the sample code

// Prepare the environment and the command
ProcessBuilder pb = new ProcessBuilder("java", "psp.u2.actividad10.Sumador");
pb.directory(new File("build/classes"));
Process p = pb.start();

Activity psp.activities.U2A4_Launcher

Create a new Java application project (package psp.activities & main class U2A4_Launcher).

Into the project create another class, U2A4_Commander with a main method that receives a program name as a unique parameter in the main. Make this application to create and run a process for the program and wait until that process has finished.

This class will return always the same value the launched program did.

System.exit() method

  • Zero. The zero status code should be used when the program execution went fine, i.e., the program is terminated successfully.
  • Non-Zero. A nonzero status code indicates abnormal termination. Java allows us to use different values for different kinds of errors.

Now, make the U2A4_Launcher class ask the user for an application name and launch the Commander class passing it the name of the application entered by the user.

Get the exitValue from Commander and show it's value, telling if the process worked fine or if it failed.

U2A4_Launcher solution
public class U2A4_Launcher {

    public static void main(String[] args) {

        // Código para pedir un programa/comando a ejecutar
        Scanner teclado = new Scanner(System.in);
        System.out.println("Introduce el programa / comando que quieres ejecutar: ");
        String comando = teclado.nextLine();

        try {
            // Preparamos el entrono de ejecución del proceso
            // Como no sabemos el contenido del comando, forzamos su conversión
            // a una lista para que no haya problemas con su ejecución
            comando = "java psp.activities.U2A4_Commander " + comando;
            ProcessBuilder pb = new ProcessBuilder(comando.split("\\s"));
            pb.directory(new File("build/classes"));

            // Lanzamos el proceso hijo
            Process p = pb.start();          

            // Esperamos a que acabe para recoger el valor de salida
            int exitValue = p.waitFor();

            if (exitValue == 0) {
                System.out.println("El comando " + pb.command().toString() + " ha finalizado bien");
            } else {
                System.out.println("El comando " + pb.command().toString() + " ha finalizado con errores. Código (" + exitValue + ")");                
            }
        } catch (InterruptedException | IOException ex) {
            System.err.println(ex.getLocalizedMessage());
            ex.printStackTrace();
        }                       
    }
}

public class U2A4_Commander {

    public static void main(String[] args) throws Exception {       
        // Lectura de información desde los parámetros de entrada 
        // Se supone que recibimos: args[0] args[1] args[2] ..... args[args.length-1 --> comando a ejecutar
        String comando = "";
        for (int i = 0; i < args.length; i++) {
            comando += args[i] + " ";
        }
        comando.trim();
                
        ProcessBuilder pb = new ProcessBuilder(comando.split("\\s"));            

        // Lanzamos el proceso hijo
        Process p = pb.start();          

        // Esperamos a que acabe para recoger el valor de salida
        int exitValue = p.waitFor();

        System.exit(exitValue);
    }    
}

Child classes programming

Every class must be coded to be run independently or just as a child process. Think that in Netbeans all classes are run as child classes from the IDE.

That's why the code, child or parent, needs to be done without thinking how they are gonna be called. The code must be independent just like processes are one from each other.

2.2.4 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 was started
  • total time spent by it and the user who created it

Getting information about the current process

Here's how we can do that for the current process:

// Get information about the current process
ProcessHandle processHandle = ProcessHandle.current();
ProcessHandle.Info processInfo = processHandle.info();

System.out.println("PID: " + processHandle.pid());
System.out.println("Arguments: " + processInfo.arguments());
System.out.println("Command: " + processInfo.command());
System.out.println("Instant: " + processInfo.startInstant());
System.out.println("Total CPU duration: " + processInfo.totalCpuDuration());
System.out.println("User: " + processInfo.user());

 
 







Getting information about a child process from parent

It is 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.

// Get information about a child process from parent
Process process = processBuilder.inheritIO().start();
ProcessHandle childProcessHandle = process.toHandle();
ProcessHandle.Info childProcessInfo = childProcessHandle.info();


 
 

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

Last updated:
Contributors: Vicente Martínez