4

Lets say I have a method classA.methodA() and its calling classB.methodB(). Now, inside the classB.methodB(), is there any way to know that its being called by classA (without passing any explicit info). I know this info is there in Java Runtime. My question is how to get the the class name of callee method ?

to make it more obvious

ClassA{

methodA(){
  ClassB b = new ClassB();
  b.methodB();

}

}


ClassB{

methodB(){
  // Code to find that its being called by ClassA
}

}
Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
Santosh
  • 17,667
  • 4
  • 54
  • 79
  • 2
    If you ever need this information, you've a flaw in the code design. Perhaps you need the strategy or visitor pattern. – BalusC Dec 01 '11 at 11:45
  • 1
    A similar thread at http://stackoverflow.com/questions/421280/in-java-how-do-i-find-the-caller-of-a-method-using-stacktrace-or-reflection – Vino Dec 01 '11 at 11:45
  • 1
    So you want the call- __er__ class name? – Viruzzo Dec 01 '11 at 11:46
  • A common alternative is to have a Context class that contains whatever state that ClassB needs to make decisions on how it will behave. You can pass this object in to ClassB's methodB, or ClassA can create its own instance of ClassB, passing in Context to the constructor. In the latter case you could have a constructor for default use that takes no Context and uses a default Context. If you don't like either of these simple approaches, the Context class can have a thread local that maintains the Context for each thread. ClassB calls a static getCurrentContext method in Context inside methodB. – Paul Jackson Dec 01 '11 at 13:33

6 Answers6

8

You can use Thread.currentThread().getStackTrace() to get a stack trace, then iterate through it to find which method is above yours in the stack.

For example (totally made up stack here), you might have a stack like this:

main()
|- Foo()
   |- Bar()
      |- MyFunction()
         |- getStackTrace()
            |- captureJavaThreadStack(...)

Starting from the bottom, you iterate up until you hit the getStackTrace() method. You then know that the calling method is two methods up from that position, i.e. MyFunction() called getStackTrace(), which means that MyFunction() will always be above getStackTrace(), and whatever called MyFunction() will be above it.

You can use the getClassName() method on the StackTraceElement class to pull out the class name from the selected stack trace element.

Docs:

http://docs.oracle.com/javase/6/docs/api/java/lang/Thread.html#getStackTrace%28%29

http://docs.oracle.com/javase/6/docs/api/java/lang/StackTraceElement.html

Polynomial
  • 27,674
  • 12
  • 80
  • 107
  • 2
    Note: This method may not be reliable. "Some virtual machines may, under some circumstances, omit one or more stack frames from the stack trace. In the extreme case, a virtual machine that has no stack trace information concerning this thread is permitted to return a zero-length array from this method.". – Thorbjørn Ravn Andersen Dec 01 '11 at 11:48
  • Are there any overheads in using this method ? – Santosh Dec 01 '11 at 11:52
  • Yes, that is true. In general, though, you can detect this and take appropriate action (fail or use a default value). – Polynomial Dec 01 '11 at 11:52
  • @Santosh - Yes, there will be an overhead involved with walking the stack. It will not be horrendously slow, but it may start to cause performance issues if you're calling it more than fifty times a second (rough estimate) or so. – Polynomial Dec 01 '11 at 11:53
  • Also note that this method internally throws an exception and uses the stack trace element from that exception to calculate the current stack trace. As such, it is obviously very slow simply calling it, let alone processing the output. – Milad Naseri Sep 04 '14 at 06:36
3

Starting Java 5.0, you can use Thread.currentThread().getStackTrace() to get a current stack trace.

To get caller class (and method) of current method:

StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
int i = 1;
while (MyClass.class.getName().equals(stackTraceElements[i].getClassName())) { i++; }

int lineNumber = stackTraceElements[i].getLineNumber();
String className = stackTraceElements[i].getClassName();
String methodName = stackTraceElements[i].getMethodName();

Please note! There is some kind of warning in javadocs regarding this method:

Some virtual machines may, under some circumstances, omit one or more stack frames from the stack trace. In the extreme case, a virtual machine that has no stack trace information concerning this thread is permitted to return a zero-length array from this method.

I have not any trouble with this piece of code on Sun (Oracle) JVM and Mac OS X JVM. Please test your code carefully to be sure it works like you expect, especially if you use any other JVM.

Aleksejs Mjaliks
  • 8,647
  • 6
  • 38
  • 44
  • Are you sure that index 0 is always the method that calls `getStackTrace()`? The documentation doesn't mention that it's guaranteed. In fact, I would expect `getStackTrace()` to be at the top of the stack, if not another internal method within it. – Polynomial Dec 01 '11 at 11:56
  • @Polynomial If you ask, then I'm not sure. I have fixed my example. Now there is a code use (it is almost copy paste from production code) to determine names of a caller method. – Aleksejs Mjaliks Dec 01 '11 at 12:03
  • The problem with that code is that, as the documentation says, you can't rely on the stack trace containing your method's stack trace element, or even any entries. In such a case your `while` loop will just spin until your array index goes out of range. – Polynomial Dec 01 '11 at 12:22
  • @Polynomial As I said, I use code in production, it isn't just an example. All over this time, I didn't get `ArrayIndexOutOfBoundsException` in this code. And result always was of this code was always as expected. I have checked once again javadoc for `getStackTrace()`. I don't see warning you tell about. Maybe I'm looking in a wrong place? I don't want to say you wrong, I just worried about my code. – Aleksejs Mjaliks Dec 01 '11 at 12:47
  • From the documentation: *"Some virtual machines may, under some circumstances, omit one or more stack frames from the stack trace. In the extreme case, a virtual machine that has no stack trace information concerning this thread is permitted to return a zero-length array from this method."* – Polynomial Dec 01 '11 at 12:48
  • @Polynomial As I understand `Some virtual machines` it is referred to different implementations of VMs, not different instances. I have no any troubles with this code on Sun (Oracle) and Apple (patched Sun/Oracle) JVM with this code. Maybe I just don't get this _some circumstances_. :) – Aleksejs Mjaliks Dec 01 '11 at 12:53
  • Whilst it doesn't seem to cause problems with major JVMs, I wouldn't be surprised if a lot of mobile JVMs disable stack tracing at least for release-build code, if not entirely. The same goes for less-common and 3rd party JVMs for systems like PowerPC and HP-UX. – Polynomial Dec 01 '11 at 12:57
  • @Aleksejs, you are depending on the JVM behaving in a certain way. If you always use the same JVM you will always see the same behavior. – Thorbjørn Ravn Andersen Dec 01 '11 at 15:35
  • I have added a note that my answer depends on my observations, and there is no guarantee it will work for others. I hope you will appreciate this. :) – Aleksejs Mjaliks Dec 01 '11 at 18:51
3

Only by inspecting the call stack which is not directly supported, but you can get an almost reliable answer by looking at either a stack trace (just create a RuntimeException) or a ThreadDump (for Java 6).

If you want to have your code behave differently based on who called it, you are most definitively heading in the wrong direction. Pass down the needed information explictly or use sub-classes to provide different behavior.

Thorbjørn Ravn Andersen
  • 73,784
  • 33
  • 194
  • 347
2

You can use the stack trace to do that. Inside the called method, do the following

Throwable t = new Throwable(); 
StackTraceElement[] stackTraceElements = t.getStackTrace(); 
String calleeMethod = stackTraceElements [0].getMethodName(); 
String callerMethodName = stackTraceElements [1].getMethodName(); 
String callerClassName = stackTraceElements [1].getClassName(); 
System.out.println("Caller :" + callerClassName + "." + callerMethodName); 
GETah
  • 20,922
  • 7
  • 61
  • 103
1

A not very reliable way is to look into

Thread.currentThread().getStackTrace()

It's not reliable, because there are various reasons why the callee is not what you'd expect, e.g. instrumentation, reflection utilities, etc. But maybe that's good enough for your use-case?

Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
0

Warning (Potentially) Hotspot only and is not public API

Try sun.reflect.Reflection.getCallerClass(int numFramesToSkip); note that this API has been deprecated.

I last used this to do some evil ASM magics to re-write the log4j call on a third party library so that we could correct its dumb logging and get it to do log4j categories properly, no I will not mention what third party library as they are a company that is very touchy about reverse engineering.

Notes:

  • This is not the stackframe, its a reference to the class that made a call from that frame
  • This method will not give line numbers and other such things (the JVM has to "de-optimise" from jit code and reconstruct the debugging stanzas for that, one reason why stacktraces are slow)
  • This does not create a stacktrace
  • On hotspot the jit actively inlines this code making it super fast (look in globals.hpp for the switch and elsewhere in the code for the proof)

I can now hear the mob screaming about the none-public API example

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
Greg Bowyer
  • 1,684
  • 13
  • 14