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);
}
}
Solution: Though Java doesn't prefer main() method called from somewhere else in the program, it does not prohibit one from doing it as well. So, in fact, we can call the main() method whenever and wherever we need to.
Call a MethodInside main , call the myMethod() method: public class Main { static void myMethod() { System.out.println("I just got executed!"); } public static void main(String[] args) { myMethod(); } } // Outputs "I just got executed!"
From the above program, we can say that Java can have multiple main methods but with the concept of overloading. There should be only one main method with parameter as string[ ] arg.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With