5

I've written a program that outputs a usage hint. It currently echos back the path to the main jar file as was originally entered on the command line.

Usage: java -jar path/to/MyJar.jar <params> ...

For completeness, I'd like to make sure that the java bit is echoed back as well, as there are various ways to access java, (beyond just the word java, and shorter than the canonical path to /us/opt/java-1.8.0-u123/bin/java)

Usage: /us/opt/java7/bin/java -jar MyJar.jar <params> ...
Usage: ./bin/java -jar MyJar.jar <params> ...
Usage: java -jar MyJar.jar <params> ...
# whatever the user typed in

How can I determine what command-line was used to evoke the JVM?
I would like the original command-line value, prior to evaluating symbolic links.

I'm not using System.getProperty("java.home") because it has no respect for the original command-line value, just the final 'canonical' location of the JVM. (Having a usage note like Usage: /us/opt/java-1.8.0-u123/jre/bin/java -jar ... would be rather verbose,
especially when using simple java on the command line.)

Is determining the command-line location of java possible using pure Java code?
(i.e. not using a wrapper script in bash)

700 Software
  • 85,281
  • 83
  • 234
  • 341
  • Possible duplicate of [Determining location of JVM executable during runtime](http://stackoverflow.com/questions/3392416/determining-location-of-jvm-executable-during-runtime) – ipsi Jul 27 '16 at 16:28
  • I wonder why the downvote. Is it because the question is unclear or because it is duplicate? I did search before asking but I kept getting results that did not solve this particular question. – 700 Software Jul 27 '16 at 17:30
  • It's not immediately clear that you were aware of the other options, or what you're trying to do that you can't do with the top-voted answer in the linked question. – ipsi Jul 27 '16 at 17:38
  • Well, I was not aware of the linked question. Now that I've looked, I'll edit my question to clarify. – 700 Software Jul 27 '16 at 18:25
  • How about http://stackoverflow.com/a/12989117/113632 ? – dimo414 Jul 27 '16 at 18:30
  • @dimo414, Gosh, that's pretty helpful. `getProperty("sun.java.command")` is a much simpler way to get the path to `MyJar.jar`, compared to what I was doing, even if it isn't completely cross-platform. However this is not the path to the `java` executable. If I could get that answer I'd be all set. :-) – 700 Software Jul 27 '16 at 18:33
  • `sun.java.command` will only work with Oracle JVM – 11thdimension Jul 28 '16 at 18:57
  • @11thdimension, Understood. I'm going to use my existing code as a fallback in case `sun.java.command` is not set. – 700 Software Jul 28 '16 at 20:04

2 Answers2

1

Use following command

jps -mlvV

This should print everything about running java processes.

Where as ps -e should give you executable path.

Following is pure Java solution, it doesn't print the actual command used to execute the application but it produces a command which will have the same effect.

import java.io.File;
import java.lang.management.ManagementFactory;
import java.util.List;
import java.util.Map.Entry;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        System.out.println("Printing command");
        createCommand();
    }
    public static void createCommand() {
        try {
            String jvm = getJvmExecutable();
            String mainClassName = findMainClass();
            String processDir = System.getProperty("user.dir");


            String arguments = getArguments();
            String classpath = ManagementFactory.getRuntimeMXBean().getClassPath();


            String command = String.format("cd %s & %s %s -classpath %s %s",processDir, jvm, arguments, classpath, mainClassName);

            System.out.println(command);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    private static String getJvmExecutable() {
        String jvm = "";
        jvm = System.getProperty("java.home");
        jvm += File.separator + "bin" + File.separator + "java";
        jvm = '"' + jvm + '"';
        return jvm;
    }

    private static String getArguments() {
        List<String> argsList = ManagementFactory.getRuntimeMXBean().getInputArguments();
        String args = argsList.stream().collect(Collectors.joining(" "));

        return args;
    }

    public static String findMainClass() throws ClassNotFoundException{
        for (Entry<Thread, StackTraceElement[]> entry : Thread.getAllStackTraces().entrySet()) {
            Thread thread = entry.getKey();
            if (thread.getThreadGroup() != null && thread.getThreadGroup().getName().equals("main")) {
                for (StackTraceElement stackTraceElement : entry.getValue()) {
                    if (stackTraceElement.getMethodName().equals("main")) {

                        try {
                            Class<?> c = Class.forName(stackTraceElement.getClassName());
                            Class[] argTypes = new Class[] { String[].class };
                            //This will throw NoSuchMethodException in case of fake main methods
                            c.getDeclaredMethod("main", argTypes);
                            return stackTraceElement.getClassName();
                        } catch (NoSuchMethodException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
        return null;
    }
}

Note: It will work with plain java projects or normal JARS, however it will not work with special classloading like WAR, OSGI or Spring Boot's classloading.

I used the findMainClass() method from this answer https://stackoverflow.com/a/8275751/5343269

Community
  • 1
  • 1
11thdimension
  • 10,333
  • 4
  • 33
  • 71
  • I'm looking for a pure Java code solution, so the application can generate its own usage script. It seems inefficient to `exec` an external process such as `jps` or `ps` for this purpose. – 700 Software Jul 27 '16 at 17:29
  • I clarified the question. I already have the path to main class, and to my jar, but what I am looking for is a path to java, which is not so terse. So if the user types `java` then the usage note can just be `java`. If the user typed a symbolic link, I just want that. I do *not* want to *always* show the 'full path' as would be returned by `System.getProperty("java.home")`. – 700 Software Jul 28 '16 at 18:10
  • I'm not sure if I understand it correctly, there can be multiple version of Java installed on the machine, that's why I have used the `java.home` to retrieve the JVM which was used to launch the process. – 11thdimension Jul 28 '16 at 18:49
  • I'm not actually trying to get the 'absolute' and 'canonical' path of the JVM. I want what the user typed in, which could be just the word `java`, or a relative path `./mydir/bin/java`, absolute path to symlink `/usr/bin/java` or full canonical path. I do not want to always reflect the full canonical path back, I want to reflect back what was typed. – 700 Software Jul 28 '16 at 20:06
0

Use this System property in code

System.getProperty("java.home");

This will return Installation directory for Java Runtime Environment (JRE)

More info here

Shettyh
  • 1,188
  • 14
  • 27