Java exec: Execute system processes with Java ProcessBuilder and Process (part 1)

I've been thinking about rewriting my old tutorial on how to execute system processes from a Java application for a while now (How to run system commands from Java applications), but it's a topic that quickly becomes complicated if you want to do it right, so I kept postponing it until I could give it some quality time in a real-world project.

I've finally had an opportunity to test this in a real-world Java application on a current project, so while I'm still not finished with this, I thought I'd share what I've learned so far.

The Apache Java exec project

Before continuing to read this article, if you don't care how things work, and you just want to execute a system command from a Java application, you may just want to take a look at the Apache exec project. While some of that code may be dated (it is JDK 1.3 compatible), it is the same code that is used by the Apache Ant project, and is not a "work in progress", as the code shown below admittedly is. (I will probably have a few releases of this in the coming weeks.)

However, if you'd like to be able to execute system commands with just a few additional classes, and you get a detailed explanation of how the classes work, I welcome you to keep reading ...

Basic Java exec with the ProcessBuilder and Process classes

When you first look at using the Java ProcessBuilder and Process to run (exec) system commands, it looks very easy. Just construct a ProcessBuilder object, tell it to start, and assign the results to a Process object, and you're done.

Unfortunately what appears to be very simple -- and what works in the simplest case -- is not something you want to do if your job depends on the results.

For instance, in their ProcessBuilder javadoc, Sun shows an example like this:

// Sun's ProcessBuilder and Process example
ProcessBuilder pb = new ProcessBuilder("myCommand", "myArg1", "myArg2");
Map<String, String> env = pb.environment();
env.put("VAR1", "myValue");
env.remove("OTHERVAR");
env.put("VAR2", env.get("VAR1") + "suffix");
pb.directory("myDir");
Process p = pb.start();

That's a nice, simple example, and it may indeed let you execute a system command, but if you want to read the standard output and standard error from your command, or supply standard input to your command, you need a much more robust solution.

A more-complete Java ProcessBuilder and Process approach

I'm not going to discuss all the possible pitfalls that you can run into when executing a system command from a Java application. That topic was already covered nicely about 10 years ago in a JavaWorld article, and to say the least, the ProcessBuilder example shown above won't work in many cases.

Instead of rehashing all those pitfalls again, what I'm going to do is share some code that I'm currently developing for a project where I execute system commands like ping, netstat, and lsof, and then read the output of those commands once they've finished executing.

Beginning with the end in mind, let's assume that we want to run the following Unix/Linux command from within a Java application:

ls -l /var/tmp

To run that command from a Java application using my new code, we'd first build and then exec the command like this:

// build my command as a list of strings
List<String> command = new ArrayList<String>();
command.add("ls");
command.add("-l");
command.add("/var/tmp");

// execute my command
SystemCommandExecutor commandExecutor = new SystemCommandExecutor(command);
int result = commandExecutor.executeCommand();

// get the output from the command
StringBuilder stdout = commandExecutor.getStandardOutputFromCommand();
StringBuilder stderr = commandExecutor.getStandardErrorFromCommand();

// print the output from the command
System.out.println("STDOUT");
System.out.println(stdout);
System.out.println("STDERR");
System.out.println(stderr);

As you can see, as a consumer of a class named SystemCommandExecutor (which I will share with you shortly), I think this is pretty simple, and also lets you get the standard output and standard error from the command you just executed. Using this class you can even execute command pipelines from Java (such as "ps aux | wc -l"), as well as sudo commands. But, first things first ...

 

Next: Java exec with ProcessBuilder, part 2 >>