24

I want to determine the class name where my application started, the one with the main() method, at runtime, but I'm in another thread and my stacktrace doesn't go all the way back to the original class.

I've searched System properties and everything that ClassLoader has to offer and come up with nothing. Is this information just not available?

Thanks.

Erik R.
  • 7,152
  • 1
  • 29
  • 39
  • 4
    I'm curious if you could explain why you are looking for this..? – matt b Jun 02 '09 at 14:41
  • 1
    For setting log levels in a common config file for multiple applications. – Erik R. Jun 02 '09 at 14:55
  • 1
    I agree with matt b. In my opinion that should never be necessary. It sounds as if your system tries to be more self-aware than is good for itself. – Joachim Sauer Jun 02 '09 at 14:56
  • 1
    @Erik: then why don't you simply set the log level in the respective main classes or pass some argument to the class responsible for setting the log level? – Joachim Sauer Jun 02 '09 at 14:57
  • @Joachim: to allow changing of log level at runtime (via file modification) without restarting the JVM. – Erik R. Jun 02 '09 at 15:00
  • @Erik, I think you might be trying to solve a side problem here rather than the main problem – matt b Jun 02 '09 at 15:00
  • 1
    You could still do a "LogLevelManager.setCurrentMainClass(ThisClass.class);" in your main classes. That's certainly more stable than trying to parse non-standard system properties or environment variables. – Joachim Sauer Jun 02 '09 at 15:45
  • I want to do this because I am writing a monitoring library and want to report data found in the monitored application's manifest. I want a simple API that allows the user to just instantiate my monitor and it finds everything it needs on its own. Also, I can't make any assumptions about who or when my monitor is instantiated, so I need something that works even if the main thread is already finished (e.g., daemon threads remaining). – Micah Zoltu Jul 25 '16 at 20:28

10 Answers10

11

See the comments on link given by Tom Hawtin. A solution is these days is (Oracle JVM only):

public static String getMainClassAndArgs() {
    return System.getProperty("sun.java.command"); // like "org.x.y.Main arg1 arg2"
}

Tested only with Oracle Java 7. More information about special cases: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4827318

Bruno Eberhard
  • 1,624
  • 16
  • 22
8

The JAVA_MAIN_CLASS environment value isn't always present depending on the platform. If you just want to get a name of the "main" class that started your Java process, you can do this:

  public static String getMainClassName()
  {
    StackTraceElement trace[] = Thread.currentThread().getStackTrace();
    if (trace.length > 0) {
      return trace[trace.length - 1].getClassName();
    }
    return "Unknown";
  } 
Dave
  • 3,273
  • 1
  • 17
  • 15
4

I figured it out. Can anyone tell me if this environment variable will always be around in other java implementations across operating systems? This on Oracle JVM yields a String like "org.x.y.ClassName"

public static String getMainClassName() {
  for (final Map.Entry<String, String> entry : System.getenv().entrySet())
    if (entry.getKey().startsWith("JAVA_MAIN_CLASS")) // like JAVA_MAIN_CLASS_13328
      return entry.getValue();
  throw new IllegalStateException("Cannot determine main class.");
}
Christian Hujer
  • 17,035
  • 5
  • 40
  • 47
Erik R.
  • 7,152
  • 1
  • 29
  • 39
3

Try using Thread.getAllStackTraces(). It returns a Map of the stack traces from all running threads, not just the current one.

Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
  • 3
    That doesn't really help, since the thread that initially ran the main class need not be running any more. – Joachim Sauer Jun 02 '09 at 14:55
  • 7
    (Doesn't help in every conceivable case) != (doesn't help at all). – Bill the Lizard Jun 02 '09 at 15:13
  • 3
    I agree and realized that after posting the comment. Still it's good to be aware of the limits of a solution. – Joachim Sauer Jun 02 '09 at 15:43
  • 1
    No hard feelings. :) After reading the revised question and several comments on other answers I realize this isn't going to solve the OP's problem. Still, I think it might be helpful to other people going down the same path. – Bill the Lizard Jun 02 '09 at 15:53
  • There's another limitation as well. The main method might have been inherited (static methods are not virtual but still they are inherited). class Bar { public static void main(String[] args) { System.out.println("Bar");}} public class Foo extends Bar {} java Foo -> runs Bar.main() and the stack trace would not tell anything about Foo. – Christian Hujer Jul 30 '16 at 22:36
2

Even if the thread with the main() method has terminated and you are not using the Oracle JVM you can still attempt to get the information from the operating system. The code below obtains the command line used to start the JVM under Linux but you could write a version for Windows, etc. You could then look at the arguments to the JVM to find the application entry point. It might be directly on the command line or you might have look for Main-Class: classname in the manifest of the specified jar. I would first use System.getProperty("sun.java.command") and friends only falling back to this mechanism if necessary.

public final static long getSelfPid() {
    // Java 9 only
    // return ProcessHandle.current().getPid();
    try {
        return Long.parseLong(new File("/proc/self").getCanonicalFile().getName());
    } catch( Exception e ) {
        return -1;
    }
}

public final static String getJVMCommandLine() {
    try {
        // Java 9 only
        // long pid = ProcessHandle.current().getPid();
        long pid = getSelfPid();
        byte[] encoded = Files.readAllBytes(Paths.get("/proc/"+pid+"/cmdline"));
        // assume ISO_8859_1, but could look in /proc/<pid>/environ for LANG or something I suppose
        String commandLine = new String( encoded, StandardCharsets.ISO_8859_1 ); 
        String modifiedCommandLine = commandLine.replace((char)0, ' ').trim();
        return modifiedCommandLine;
    } catch( Exception e ) {
        return null;
    }
}`
2

Given the clarification, I suggest using the "Parameterisation from Above" idiom. You have the information to start with, keep hold of it.

I did put in an RFE 4827318 (six years ago!) for something like this for use with test runners.

Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
1

Another way to get main class is look for that class on Thread.getAllStackTraces, so you could find it even inside jars, it works on any SDK (Open, Oracle...):

private static Class<?> mainClass = null;

public static Class<?> getMainClass()
{
    if (mainClass == null)
    {
        Map<Thread, StackTraceElement[]> threadSet = Thread.getAllStackTraces();
        for (Map.Entry<Thread, StackTraceElement[]> entry : threadSet.entrySet())
        {
            for (StackTraceElement stack : entry.getValue())
            {
                try
                {
                    String stackClass = stack.getClassName();
                    if (stackClass != null && stackClass.indexOf("$") > 0)
                    {
                        stackClass = stackClass.substring(0, stackClass.lastIndexOf("$"));
                    }
                    Class<?> instance = Class.forName(stackClass);
                    Method method = instance.getDeclaredMethod("main", new Class[]
                    {
                        String[].class
                    });
                    if (Modifier.isStatic(method.getModifiers()))
                    {
                        mainClass = instance;
                        break;
                    }
                }
                catch (Exception ex)
                {
                }
            }
        }
        return mainClass;
    }
}
jalopezsuarez
  • 414
  • 4
  • 13
  • Checking the class for the `main(String[])` method is a nice idea. However checking every class of the stack trace is a bit much. On the main thread the main class is always the last stacktrace element. – Robert Apr 25 '18 at 15:31
1

How about something like:

Map<Thread,StackTraceElement[]> stackTraceMap = Thread.getAllStackTraces();
for (Thread t : stackTraceMap.keySet())
{
    if ("main".equals(t.getName()))
    {
        StackTraceElement[] mainStackTrace = stackTraceMap.get(t);
        for (StackTraceElement element : mainStackTrace)
        {
            System.out.println(element);
        }
    }
}

This will give you something like

java.lang.Object.wait(Native Method)
java.lang.Object.wait(Object.java:231)
java.lang.Thread.join(Thread.java:680)
com.mypackage.Runner.main(Runner.java:10)

The main thread is probably not guarenteed to be called "main" though - might be better to check for a stack trace element that contains (main

Edit if the main thread has exited, this is no good!

Harry Lime
  • 29,476
  • 4
  • 31
  • 37
0

Here's what I'm using, for situations where you don't control the main:

public static Class<?> getMainClass() {
  // find the class that called us, and use their "target/classes"
  final Map<Thread, StackTraceElement[]> traces = Thread.getAllStackTraces();
  for (Entry<Thread, StackTraceElement[]> trace : traces.entrySet()) {
    if ("main".equals(trace.getKey().getName())) {
      // Using a thread named main is best...
      final StackTraceElement[] els = trace.getValue();
      int i = els.length - 1;
      StackTraceElement best = els[--i];
      String cls = best.getClassName();
      while (i > 0 && isSystemClass(cls)) {
        // if the main class is likely an ide,
        // then we should look higher...
        while (i-- > 0) {
          if ("main".equals(els[i].getMethodName())) {
            best = els[i];
            cls = best.getClassName();
            break;
          }
        }
      }
      if (isSystemClass(cls)) {
        i = els.length - 1;
        best = els[i];
        while (isSystemClass(cls) && i --> 0) {
          best = els[i];
          cls = best.getClassName();
        }
      }
      try {
        Class mainClass = Class.forName(best.getClassName());
        return mainClass;
      } catch (ClassNotFoundException e) {
        throw X_Util.rethrow(e);
      }
    }
  }
  return null;
}

private static boolean isSystemClass(String cls) {
  return cls.startsWith("java.") ||
      cls.startsWith("sun.") ||
      cls.startsWith("org.apache.maven.") ||
      cls.contains(".intellij.") ||
      cls.startsWith("org.junit") ||
      cls.startsWith("junit.") ||
      cls.contains(".eclipse") ||
      cls.contains("netbeans");
}
Ajax
  • 2,465
  • 23
  • 20
0

I suggest to put this information into a system property. This is usually simple to do when you start your application from a script.

If you can't do that, I suggest to set the property in the main() method of every application. The most simple way here would be to have every app derive it's "main class" from a common base class and run an init step in there. I often do this for command line processing:

public class Demo extends Main {
    main(String[] args) {
        Main._main(new Demo (), args);
    }

    // This gets called by Main._main()
    public void run (String[] args) {
    }
}
Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820