ProcessBuilder vs Runtime.exec()

18,193

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.

Share:
18,193
blastthisinferno
Author by

blastthisinferno

Updated on June 04, 2022

Comments

  • blastthisinferno
    blastthisinferno almost 2 years

    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?

  • Jonathan
    Jonathan over 13 years
    I'm pretty sure that Java handles splitting the arguments in either ProcessBuilder or Runtime.exec(), but otherwise correct.
  • Admin
    Admin over 13 years
    @Jonathan, a quick look at Runtime shows you are correct - StringTokenizer is used to break up the string on whitespace. Which means if you've got whitespace in your executable's path, you will need to use Runtime.exec(String[]).