7

I need a clean way to start many instances of a Java program with a GUI, and I want to do it programmatically. The "program" i want to run is just a .class file (a compiled .java file with a main method), it should show a GUI and run independently from the others (as its own process). I also need to pass that program some arguments.

Check EDIT5 for the complete working solution code.

Here's the class that's supposed to start the many processes

package startothermain;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Starter {

    public static void main(String[] args) {
        int starts = 4;

        for (int i = 0; i < starts; ++i) {
            System.out.println("Starting an app");
            ProcessBuilder pb = new ProcessBuilder("java.exe", "-cp", "bin", "Started", "arg0");
            try {
                pb.start();
            } catch (IOException ex) {
                Logger.getLogger(Starter.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

This is the class that's supposed to be started and display a GUI

package startothermain;

import javax.swing.JOptionPane;

public class Started {

    public static void main(String[] args) {
        JOptionPane.showMessageDialog(null, args[0]);
    }
}

I tried both ProcessBuilder and Runtime.getRuntime() suggestions I found in other answers, but they don't seem to work. I always get some kind of "not found" error, like this one

SEVERE: null
java.io.IOException: Cannot run program "java.exe": error=2, No such file or directory
    at java.lang.ProcessBuilder.start(ProcessBuilder.java:1041)
    at startothermain.Starter.main(Starter.java:35)
Caused by: java.io.IOException: error=2, No such file or directory
    at java.lang.UNIXProcess.forkAndExec(Native Method)
    at java.lang.UNIXProcess.<init>(UNIXProcess.java:135)

I'm working from NetBeans and clicking the Run button, so the .java files should be compiled properly and in the same folder. I hope this is no side problem with the src/build folders created by the IDE.

EDIT: I was trying to find the java.exe, but I'm on Linux. Sigh. I changed it for "java", but I still have the same problem. The path variable should be set. Moreover, I'm worried that, if I give it a full path, it will become unportable.

Trying to get the JAVA_HOME on Linux

System.getenv("JAVA_HOME"); // returns null
System.getProperty("java.home"); // returns /usr/lib/jvm/java-7-openjdk-amd64/jre

And on Windows

System.getenv("JAVA_HOME"); // returns C:\Program Files\Java\jdk1.7.0_51
System.getProperty("java.home"); // returns C:\Program Files\Java\jdk1.7.0_51\jre

EDIT2: This new code does NOT spawn errors, but does not open any GUI either. I tried this on both on Windows and Linux, with the same results.

String javaHome = System.getProperty("java.home");
ProcessBuilder pb = new ProcessBuilder(javaHome + "/bin/java", "-cp", "bin", "Started", "arg0");

EDIT3: I redirected the error and output streams of the processbuilder (not of the crerated processes) and it turns out that the Java ClassLoader can't find the class. I guess the problem is not JAVA_HOME, but rather a path problem.

Here's the new code

package startothermain;

import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Starter {

    public static void main(String[] args) {
        int starts = 4;
        System.out.println(System.getProperty("java.home")); // prints C:\Program Files\Java\jdk1.7.0_51\jre
        System.out.println(System.getenv("JAVA_HOME")); // prints C:\Program Files\Java\jdk1.7.0_51

        System.out.println("Working Directory = "
                + System.getProperty("user.dir")); // prints C:\Users\Agostino\Documents\NetBeansProjects\StartOtherMain

        for (int i = 0; i < starts; ++i) {
            System.out.println("Starting an app");
            ProcessBuilder pb = new ProcessBuilder("java", "build.classes.startothermain.Started");
            File log = new File("log");
            pb.redirectOutput(log);
            pb.redirectError(log);
            try {
                pb.start();
            } catch (IOException ex) {
                Logger.getLogger(Starter.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

In the log file I find this error

java.lang.NoClassDefFoundError: build/classes/startothermain/Started (wrong name: startothermain/Started)
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:800)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:449)
    at java.net.URLClassLoader.access$100(URLClassLoader.java:71)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
    at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:482)
Exception in thread "main" 

EDIT4: now the code works, but if I try to use a .jar library in the Started class, it can't be found.

See this Started class that uses a .jar lib (JavaTuples) that is added to the project libraries through NetBeans

package startothermain;

import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Starter {

    public static void main(String[] args) {
        int starts = 1;

        for (int i = 0; i < starts; ++i) {
            System.out.println("Starting an app");
            ProcessBuilder pb = new ProcessBuilder();

            String fullClassName = Started.class.getName();
            pb.command("java", "-cp", "./build/classes", fullClassName, "myArg");

            File log = new File("log");
            pb.redirectOutput(log);
            pb.redirectError(log);
            try {
                pb.start();
            } catch (IOException ex) {
                Logger.getLogger(Starter.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

Fixed Starter class

package startothermain;

import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Starter {

    public static void main(String[] args) {
        int starts = 1;

        for (int i = 0; i < starts; ++i) {
            System.out.println("Starting an app");
            ProcessBuilder pb = new ProcessBuilder();

            String fullClassName = Started.class.getName();
            pb.command("java", "-cp", "./build/classes", fullClassName, "myArg");

            File log = new File("log");
            pb.redirectOutput(log);
            pb.redirectError(log);
            try {
                pb.start();
            } catch (IOException ex) {
                Logger.getLogger(Starter.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

The error in the ProcessBuilder log

Exception in thread "main" java.lang.NoClassDefFoundError: org/javatuples/Pair
    at startothermain.Started.main(Started.java:28)
Caused by: java.lang.ClassNotFoundException: org.javatuples.Pair
    at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
    ... 1 more

EDIT5: Working code

Starter class

package startothermain;

import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Starter {

    public static void main(String[] args) {
        int starts = 1;

        for (int i = 0; i < starts; ++i) {
            System.out.println("Starting an app");
            ProcessBuilder pb = new ProcessBuilder();

            String fullClassName = Started.class.getName();
            String pathToClassFiles = new File("./build/classes").getPath();
            String pathSeparator = File.pathSeparator; // ":" on Linux, ";" on Windows
            String pathToLib = new File("./lib/javatuples-1.2.jar").getPath();
            pb.command("java", "-cp", pathToLib + pathSeparator + pathToClassFiles, fullClassName, "myArg");

            File log = new File("log" + i + ".txt"); //debug log for started process
            try {
                pb.redirectOutput(log);
                pb.redirectError(log);
                pb.start();
            } catch (IOException ex) {
                Logger.getLogger(Starter.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

Started class

package startothermain;

import javax.swing.JOptionPane;
import org.javatuples.Pair;

public class Started {

    public static void main(String[] args) {
        Pair<String, Integer> pair = Pair.with("One", 1);
        JOptionPane.showMessageDialog(null, args[0] + " " + pair);
    }
}
Agostino
  • 2,723
  • 9
  • 48
  • 65
  • Your stack trace seems to indicate that it cannot find java.exe, try specifying the absolute path. – turbo Jan 17 '14 at 19:13
  • Or adding java.exe to your system filepath. Any programs you use frequently are nice to have in there. – William Morrison Jan 17 '14 at 19:14
  • As you are running some kind of *nix or Mac OS try to use something like `"/bin/bash", "-c", "/usr/bin/java -cp ..."` command if `/usr/bin/java", "-cp", ...` does not work for you. The primer call would execute the java command in your terminal – Roman Vottner Jan 17 '14 at 19:40
  • @user1600770 you could lookup the `JAVA_HOME` [environment variable](http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/System.html) and then invoke the java executable in the `/bin` directory. Depending on the [OS](http://docs.oracle.com/javase/tutorial/essential/environment/sysprop.html) you are running in – Roman Vottner Jan 17 '14 at 19:45
  • @RomanVottner I updated the question with my tries regarding that – Agostino Jan 17 '14 at 19:59
  • There's no javaw on Linux, either -- just use "java". – Ernest Friedman-Hill Jan 17 '14 at 20:04
  • As Linux often separates the executable from the "installation directory" it is obviously located in `/usr/bin/java` as you have stated in a comment before. So either just use `"/usr/bin/java", "-cp",...` or as mentioned in a comment before invoke the command by sending the command to your shell `"/bin/bash", "-c", "/usr/bin/java -cp ..."`. And you should set (export) your JAVA_HOME! – Roman Vottner Jan 17 '14 at 20:12
  • @ErnestFriedman-Hill good catch, still not working though. I got to the point I have the code running without errors, but without opening any GUI either (which it should). – Agostino Jan 17 '14 at 20:13
  • @RomanVottner the JAVA_HOME should be set by the system admins (I'm on a workstation), if that's the problem, I can ask them to. Yet `"/usr/bin/java", "-cp",...` only half-work (executes with no errors, but does not open any of the GUIs). EDIT: java -version works in Bash, are you sure it's not set? – Agostino Jan 17 '14 at 20:21
  • In Linux (as well as in Windows) you can also export a [(user) environment variable](http://unix.stackexchange.com/questions/21598/how-do-i-set-a-user-environment-variable-permanently-not-session) without root/Admin privileges. – Roman Vottner Jan 17 '14 at 20:24
  • Do you handle your [in- and output-streams](http://www.javaworld.com/article/2071275/core-java/when-runtime-exec---won-t.html) appropriately? – Roman Vottner Jan 17 '14 at 20:41
  • I just tried on my Windows machine, where JAVA_HOME is set. The new code in EDIT2 it executes without errors, but without opening anything (same problem as on Linux). The code I'm using is pretty much the one I posted. The Started class does not use any stream, it should just open a popup. – Agostino Jan 18 '14 at 10:15
  • @RomanVottner Found a new problem, check EDIT3 – Agostino Jan 19 '14 at 15:25
  • Is `build.classes.startothermain.Started` the fully-qualified name of the class? (so the package is `build.classes.startothermain`?) If the package is only `startothermain` and the class `Started` this might be the error. More information [here available](http://stackoverflow.com/questions/7509295/noclassdeffounderror-wrong-name). I guess the command should rather be `"java", "-cp", "./build/classes", "startothermain.Started"` – Roman Vottner Jan 19 '14 at 16:00
  • @RomanVottner no, the fully-qualified name is just startothermain.Started. I changed the working directory before launching the java command in order to make it work. Your version works too. If you want to post an answer I'll accept it instead of mine. Thanks. – Agostino Jan 19 '14 at 16:21

2 Answers2

2

As on your request I summarize my comments in an answer.

Your first problem was that you tried to invoke java.exe on a Linux or Mac box, which does not stick to the Microsoft convention of including the file type into the name. Therefore Linux and Mac usually use java instead.

The path to the executable can vary also from computer to computer. Therefore the probably most generic way is to locate the java executable using the JAVA_HOME environment variable which can be fetched in Java via System.getenv("JAVA_HOME"); or via System.getProperty("java.home"); Note however that they might not obviously point to the desired directory. Especially some Linux distributions place all binary files inside /bin or /usr/bin and therefore ${JAVA_HOME}/bin/java might not be available. In that case you should create a (hard)link to the executable.

If the path is not set you can set it manually in the session of your console you are executing your application (on Windows set JAVA_HOME=C:\Program Files\Java (or setx on newer Windows versions) on Linux export JAVA_HOME=/opt/java). This can also be done with user-priviledges

It is further strongly recommended to take care of in- and output-streams when dealing with processes. The linked article explains in depth why you should take care of it - not only to catch exceptions thrown by the invoked process.

On invoking a process (especially with the Java executable) you have a couple of options: You can either invoke the Java process directly (assuming Java is on your PATH) with new ProcessBuilder("java", "-cp", "path/to/your/binaries", "package.ClassName"); or you can invoke the Java executable via a shell - on Linux f.e. you could also use new ProcessBuilder("/bin/bash", "-c", "java -cp path/to/your/binaries package.ClassName"); (though the primer one is obviously preferable).

On loading classes/libraries dynamically you have to use the fully qualified class name. So, if you invoke the Java process in the root-directory of your project and your building-tool generates the class-files inside of ./build/classes and your class Test is located in package testpackage you will end up with the following set: ./build/classes/testpackage/Test.class. To start a new Java process which invokes the main method included in your Test.class you have to use the following command: java -cp ./build/classes testpackage.Test else Java will not be able to find the class definition of Test and execute the main-method.

Missing dependencies have to be added to the classpath (-cp ...) segment of the invoking Java command. F.e: java -cp lib/jar1.jar;lib/jar2.jar;build/classes/* package.ClassName. Multiple archives or directories are separated by a ; and an asterisk * is also possible to include everything within a directory.

One note left: if you ever try to ship your "application" you will need to adapt this code to a more generic version (property file f.e) as this will probably fail with high probability as the path might be totally different.

If I have something forgotten, please let me know.

Community
  • 1
  • 1
Roman Vottner
  • 12,213
  • 5
  • 46
  • 63
  • +1 thanks. This works, but I just discovered another (side) problem. Check EDIT4. When I start the process from outside, it can't find a simple library. Should I start another question? – Agostino Jan 19 '14 at 16:58
  • you need to provide the jar which contains the required classes in the `-cp ...` segment of the Java command you invoke. Example: `java -cp lib/jar1.jar;lib/jar2.jar;build/classes package.ClassName` or simply `java -cp lib/*;build/classes package.ClassName` – Roman Vottner Jan 19 '14 at 17:04
0

Did you try to run java.exe from command prompt if it does not work there as well you need to set you java path to the JAVA Installation this can be done by setting variable JAVA_PATH in system variables and this should point to your Jdk bin folder.

If this does not wok then I think you need to provide full path to JAVA.exe as the program is trying to find this file and it is not able to find the file and it gives error

On contrary you can try making threads that would be a better solution because threads use less resources and are more efficient

  • I guess the JAVA_PATH is set, when I run "which java" in the terminal I get "/usr/bin/java". Making threads is not an option as I want to start completely independent programs and then close the Starter. I'm open to suggestions that don't require calling java.exe, if they exist. BTW I'm on Linux, why am I calling an exe?! – Agostino Jan 17 '14 at 19:29
  • Ok I got your problem so your program is trying to find java.exe which it is not able to find. as a file so you need to give absolute path to java. Moreover if you want to close the starter application use JAVAW because it does not open a black terminal window when you try to execute the program. – vidit bhatia Jan 17 '14 at 19:32
  • @user1600770 What system are you running, java is located at `/usr/bin/java` so either *nix or Mac and you try to invoke `java.exe` - which is a windows executable – Roman Vottner Jan 17 '14 at 19:35
  • I tried "javaw" instead of "java.exe", but I still get the "not found error". Is there a way to do this in a portable way? – Agostino Jan 17 '14 at 19:35
  • yeah @Roman Vottner is correct you need to specify full path to the java executable for your platform – vidit bhatia Jan 17 '14 at 19:36