javaexternal-processruntime.execprocessbuilderinkscape

ProcessBuilder vs Runtime.exec()


I'm trying to create a frontend app in Java to handle batch SVG conversions using Inkscape's command line feature. I'm taking and updating the code from https://sourceforge.net/projects/conversionsvg/. The way the original developer handled calling Inkscape by Runtime.getRuntime().exec(String). The issue I'm running into is some inconsistencies between using methodA and methodB. I created a simple java test project to demonstrate the different actions being performed.

CallerTest.java

package conversion;

import java.io.IOException;

public class CallerTest {
  
    static String pathToInkscape = "\"C:\\Program Files\\Inkscape\\inkscape.exe\"";  
    
    public static void main(String[] args) {
      
      ProcessBuilderCaller processBuilder = new ProcessBuilderCaller();
      RuntimeExecCaller runtimeExec = new RuntimeExecCaller();
      
      // methodA() uses one long command line string
      try {
        
        String oneLongString_ProcessBuilder = pathToInkscape + " -f \"C:\\test.svg\" -D -w 100 -h 100 -e \"C:\\ProcessBuilder-methodB.png\"";
        String oneLongString_RuntimeExec =    pathToInkscape + " -f \"C:\\test.svg\" -D -w 100 -h 100 -e \"C:\\RuntimeExec-methodA.png\"";
        
//        processBuilder.methodA(oneLongString_ProcessBuilder);
        runtimeExec.methodA(oneLongString_RuntimeExec);
        
      } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
      
      // methodB() uses an array containing the command and the options to pass to the command
      try {

        String[] commandAndOptions_ProcessBuilder = {pathToInkscape, " -f \"C:/test.svg\" -D -w 100 -h 100 -e \"C:\\ProcessBuilder-methodB.png\""};
        String[] commandAndOptions_RuntimeExec =    {pathToInkscape, " -f \"C:/test.svg\" -D -w 100 -h 100 -e \"C:\\RuntimeExec-methodB.png\""};
        
        processBuilder.methodB(commandAndOptions_ProcessBuilder);
//        runtimeExec.methodB(commandAndOptions_RuntimeExec);
        
      } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    }
}

RuntimeExecCaller.java

package conversion;

import java.io.IOException;

public class RuntimeExecCaller {
    Process process;
    
    // use one string
    public void methodA(String oneLongString) throws IOException {
      process = Runtime.getRuntime().exec(oneLongString);
    }
    
    // use the array
    public void methodB(String[] commandAndOptions) throws IOException {
      process = Runtime.getRuntime().exec(commandAndOptions);
    }
}

ProcessBuilderCaller.java

package conversion;

import java.io.IOException;

public class ProcessBuilderCaller {
    Process process;
    
    // use one string
    public void methodA(String oneLongString) throws IOException {
      process = new ProcessBuilder(oneLongString).start();
    }
    
    // use the array
    public void methodB(String[] commandAndOptions) throws IOException {
      process = new ProcessBuilder(commandAndOptions).start();
    }
}

Result

Both methodA(String) calls work, but when methodB(String[]) is called Inkscape is being started and the arguments are being passed incorrectly. After methodB(String[]) executes I get an Inkscape error dialog for each saying

Failed to load the requested file -f C:/test.svg -D -w 100 -h 100 -e C:\RuntimeExec-methodB.png

Failed to load the requested file -f C:/test.svg -D -w 100 -h 100 -e C:\ProcessBuilder-methodB.png

and when I click Close on the dialog, Inkscape pops up with a new blank document. So, I guess I have a few questions:

What is the difference between Runtime.getRuntime().exec(String) and Runtime.getRuntime().exec(String[])?

JavaDoc says that Runtime.exec(String) calls Runtime.exec(command, null) (which is Runtime.exec(String cmd, String[] envp)) which in turn calls Runtime.exec(cmdarray, envp) (which is Runtime.exec(String[] cmdarray, String[] envp)). So, if Runtime.getRuntime().exec(String) is calling Runtime.exec(String[]) anyways, why am I getting different results when using different methods?

Is something happening behind the scenes where Java sets up the environment differently depending on which method is called?


Solution

  • I suspect your problem stems from the way you're specifying your argument list. Essentially, you're passing "-f C:/test.svg -D -w 100 -h 100 -e C:\RuntimeExec-methodB.png" as one single argument to Inkscape.

    What you need to do is pass the arguments individually, like so:

    String[] commandAndOptions_ProcessBuilder = {pathToInkscape, "-f", "C:\\est.svg", "-D", "-w", "100", "-h", "100", "-e", "C:\\ProcessBuilder-methodB.png"};
    String[] commandAndOptions_RuntimeExec = {pathToInkscape, "-f", "C:\\test.svg", "-D", "-w", "100", "-h", "100", "-e","C:\\RuntimeExec-methodB.png"};
    

    Roughly speaking, when you use Runtime.exec(String), the value you pass in gets evaluated by the shell, which parses out the argument list. When you use Runtime.exec(String[]), you're providing the argument list, so it doesn't need processing. A benefit of doing this is that you don't have to escape values special to the shell, as the arguments will not be evaluated by it.