85

I want to get the caller class of the method, i.e.

class foo{

  bar();

}

In the method bar, I need to get the class name foo, and I found this method:

Class clazz = sun.reflect.Reflection.getCallerClass(1);

However, even though getCallerClass is public, when I try to call it Eclipse says:

Access restriction: The method getCallerClass() from the type Reflection is not accessible due to restriction on required library C:\Program Files\Java\jre7\lib\rt.jar

Are there any other choices?

Pacerier
  • 86,231
  • 106
  • 366
  • 634
Foredoomed
  • 2,219
  • 2
  • 21
  • 39

10 Answers10

120

You can generate a stack trace and use the informations in the StackTraceElements.

For example an utility class can return you the calling class name :

public class KDebug {
    public static String getCallerClassName() { 
        StackTraceElement[] stElements = Thread.currentThread().getStackTrace();
        for (int i=1; i<stElements.length; i++) {
            StackTraceElement ste = stElements[i];
            if (!ste.getClassName().equals(KDebug.class.getName()) && ste.getClassName().indexOf("java.lang.Thread")!=0) {
                return ste.getClassName();
            }
        }
        return null;
     }
}

If you call KDebug.getCallerClassName() from bar(), you'll get "foo".

Now supposing you want to know the class of the method calling bar (which is more interesting and maybe what you really wanted). You could use this method :

public static String getCallerCallerClassName() { 
    StackTraceElement[] stElements = Thread.currentThread().getStackTrace();
    String callerClassName = null;
    for (int i=1; i<stElements.length; i++) {
        StackTraceElement ste = stElements[i];
        if (!ste.getClassName().equals(KDebug.class.getName())&& ste.getClassName().indexOf("java.lang.Thread")!=0) {
            if (callerClassName==null) {
                callerClassName = ste.getClassName();
            } else if (!callerClassName.equals(ste.getClassName())) {
                return ste.getClassName();
            }
        }
    }
    return null;
 }

Is that for debugging ? If not, there may be a better solution to your problem.

Jared Rummler
  • 37,824
  • 19
  • 133
  • 148
Denys Séguret
  • 372,613
  • 87
  • 782
  • 758
  • 16
    This answer is far superior to the accepted one and, I believe, addresses the intention of the original question more. At the least, it solves the question that brought me here. Thanks. – Inversus Dec 01 '13 at 11:04
  • 1
    getCallerCallerClassName() helped me to generate log perfectly using log4j . Logger.getLogger(getCallerCallerClassName()).info("expected info") Thanks alot. – Sugat Mankar Aug 04 '15 at 11:37
  • 1
    can you elaborate on your method to get the caller. i do see that element[0] might be the Thread class, and the check inside is in cases that there is another class before. but why cut out the first class that is not the current and not Thread? what about the current class calling its own method? – benez Sep 11 '15 at 16:30
  • @benez The first stack element will be the `getCallerCallerClassName()` enclosing class. Run it in a debugger and you'll see that quickly. – Matthieu Mar 20 '19 at 14:44
57

StackTrace

This Highly depends on what you are looking for... But this should get the class and method that called this method within this object directly.

  • index 0 = Thread
  • index 1 = this
  • index 2 = direct caller, can be self.
  • index 3 ... n = classes and methods that called each other to get to the index 2 and below.

For Class/Method/File name:

Thread.currentThread().getStackTrace()[2].getClassName();
Thread.currentThread().getStackTrace()[2].getMethodName();
Thread.currentThread().getStackTrace()[2].getFileName();

For Class:

Class.forName(Thread.currentThread().getStackTrace()[2].getClassName())

FYI: Class.forName() throws a ClassNotFoundException which is NOT runtime. Youll need try catch.

Also, if you are looking to ignore the calls within the class itself, you have to add some looping with logic to check for that particular thing.

Something like... (I have not tested this piece of code so beware)

StackTraceElement[] stes = Thread.currentThread().getStackTrace();
for(int i=2;i<stes.length;i++)
  if(!stes[i].getClassName().equals(this.getClass().getName()))
    return stes[i].getClassName();

StackWalker

StackWalker StackFrame

Note that this is not an extensive guide but an example of the possibility.

Prints the Class of each StackFrame (by grabbing the Class reference)

StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE)
    .forEach(frame -> System.out.println(frame.getDeclaringClass()));

Does the same thing but first collects the stream into a List. Just for demonstration purposes.

StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE)
    .walk(stream -> stream.collect(Collectors.toList()))
    .forEach(frame -> System.out.println(frame.getDeclaringClass()));
Atspulgs
  • 1,359
  • 11
  • 9
  • Is it expensive to do get informations from the stacktrace? – Daniel Eisenreich Sep 26 '19 at 06:53
  • I don't actually know, I haven't tested and compared the methods. I would like to believe that fetching the Class reference from StackWalker would be quicker than looking it up from a name though. – Atspulgs Sep 26 '19 at 07:40
  • 3
    The answer is in this comment: https://stackoverflow.com/questions/11306811/how-to-get-the-caller-class-in-java/34948763?noredirect=1#comment58209278_35083181 – Daniel Eisenreich Sep 27 '19 at 04:29
42

To get caller/called class name use below code, it works fine for me.

String callerClassName = new Exception().getStackTrace()[1].getClassName();
String calleeClassName = new Exception().getStackTrace()[0].getClassName();
logi-kal
  • 7,107
  • 6
  • 31
  • 43
Hitesh Bhalala
  • 2,872
  • 23
  • 40
16

SecurityManager has a protected method getClassContext

By creating a utility class which extends SecurityManager, you can access this.

public class CallingClass extends SecurityManager {
    public static final CallingClass INSTANCE = new CallingClass();

    public Class[] getCallingClasses() {
        return getClassContext();
    }
}

Use CallingClass.INSTANCE.getCallingClasses() to retrieve the calling classes.

There is also a small library (disclaimer: mine) WhoCalled which exposes this information. It uses Reflection.getCallerClass when available, else falls back to SecurityManager.

Luna
  • 1,447
  • 1
  • 18
  • 32
  • Interesting. Reflection.getCallerClass() is deprecated in J8, but I'm curious to know if you've done performance testing on that vs. the SecurityManager subclass approach vs. Thread.currentThread().getStackTrace()[n] ? – mwoodman Feb 01 '16 at 17:53
  • 2
    @mwoodman Added JMH benchmarks. The results are here: https://github.com/nallar/WhoCalled/issues/1#issuecomment-180750822 – Luna Feb 06 '16 at 12:25
  • 11
    TL;DR of benchmarks: Thread.currentThread().getStackTrace() is 20 times slower than SecurityManager.getClassContext() which is 5 times slower than Reflection.getCallingClass() which takes ~85ns/operation. – Luna Feb 06 '16 at 23:28
  • 1
    Would be a great move to add support for using Java 9‘s `StackWalker`, when available. – Holger Jun 18 '19 at 10:44
5

I know this is an old question but I believed the asker wanted the class, not the class name. I wrote a little method that will get the actual class. It is sort of cheaty and may not always work, but sometimes when you need the actual class, you will have to use this method...

/**
     * Get the caller class.
     * @param level The level of the caller class.
     *              For example: If you are calling this class inside a method and you want to get the caller class of that method,
     *                           you would use level 2. If you want the caller of that class, you would use level 3.
     *
     *              Usually level 2 is the one you want.
     * @return The caller class.
     * @throws ClassNotFoundException We failed to find the caller class.
     */
    public static Class getCallerClass(int level) throws ClassNotFoundException {
        StackTraceElement[] stElements = Thread.currentThread().getStackTrace();
        String rawFQN = stElements[level+1].toString().split("\\(")[0];
        return Class.forName(rawFQN.substring(0, rawFQN.lastIndexOf('.')));
    }
nulldev
  • 555
  • 6
  • 16
3

This is the most efficient way to get just the callers class. Other approaches take an entire stack dump and only give you the class name.

However, this class in under sun.* which is really for internal use. This means that it may not work on other Java platforms or even other Java versions. You have to decide whether this is a problem or not.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
3

The error message the OP is encountering is just an Eclipse feature. If you are willing to tie your code to a specific maker (and even version) of the JVM, you can effectively use method sun.reflect.Reflection.getCallerClass(). You can then compile the code outside of Eclipse or configure it not to consider this diagnostic an error.

The worse Eclipse configuration is to disable all occurrences of the error by:

Project Properties / Java Compiler / Errors/Warnings / Enable project specific settings set to checked / Deprecated and restrited API / Forbidden reference (access rules) set to Warning or Ignore.

The better Eclipse configuration is to disable a specific occurrence of the error by:

Project Properties / Java Build Path / Libraries / JRE System Library expand / Access rules: select / Edit... / Add... / Resolution: set to Discouraged or Accessible / Rule Pattern set to sun/reflect/Reflection.

Mario Rossi
  • 7,651
  • 27
  • 37
  • I don't know that this is correct, I believe the issue is likely related to the symbol file (http://stackoverflow.com/questions/4065401/using-internal-sun-classes-with-javac), I run into similar errors/warnings with intelliJ. I think the problem is not eclipse but javac. _I'm not suggesting you use the -Dignore.symbol.file option_, but I do think its worth being aware of, and it might allow you access to that method. – Groostav Sep 24 '15 at 02:40
  • @Grosstav: Eclipse does not use javac but its own compiler, so it would not be affected by it. Maybe what you mean is that the feature is also present in javac. The link you provide shows how to disable the feature in javac in the same way that my answer shows how to disable it in Eclipse (specifically its java compiler). – Mario Rossi Mar 27 '16 at 06:27
2

Find below a simple example illustrating how to get class and method names.

public static void main(String args[])
   {
      callMe();
   }

   void callMe()
   {
      try
      {
         throw new Exception("Who called me?");
      }
      catch( Exception e )
      {
         System.out.println( "I was called by " + 
                             e.getStackTrace()[1].getClassName() + 
                             "." +
                             e.getStackTrace()[1].getMethodName() + 
                             "()!" );
      }
   }

e has getClassName(), getFileName(), getLineNumber() and getMethodName()...

Jens
  • 67,715
  • 15
  • 98
  • 113
mrd
  • 2,095
  • 6
  • 23
  • 48
1

Since I currently have the same problem here is what I do:

  1. I prefer com.sun.Reflection instead of stackTrace since a stack trace is only producing the name not the class (including the classloader) itself.

  2. The method is deprecated but still around in Java 8 SDK.

// Method descriptor #124 (I)Ljava/lang/Class; (deprecated) // Signature: (I)Ljava/lang/Class<*>; @java.lang.Deprecated public static native java.lang.Class getCallerClass(int arg0);

  1. The method without int argument is not deprecated

// Method descriptor #122 ()Ljava/lang/Class; // Signature: ()Ljava/lang/Class<*>; @sun.reflect.CallerSensitive public static native java.lang.Class getCallerClass();

Since I have to be platform independent bla bla including Security Restrictions, I just create a flexible method:

  1. Check if com.sun.Reflection is available (security exceptions disable this mechanism)

  2. If 1 is yes then get the method with int or no int argument.

  3. If 2 is yes call it.

If 3. was never reached, I use the stack trace to return the name. I use a special result object that contains either the class or the string and this object tells exactly what it is and why.

[Summary] I use stacktrace for backup and to bypass eclipse compiler warnings I use reflections. Works very good. Keeps the code clean, works like a charm and also states the problems involved correctly.

I use this for quite a long time and today I searched a related question so

Martin Kersten
  • 5,127
  • 8
  • 46
  • 77
1

i am using the following method to get the caller for a specific class from the stacktrace:

package test.log;

public class CallerClassTest {

    public static void main(final String[] args) {
        final Caller caller = new Caller(new Callee());
        caller.execute();
    }

    private static class Caller {

        private final Callee c;

        public Caller(final Callee c) {
            this.c = c;
        }

        void execute() {
            c.call();
        }
    }

    static class Callee {

        void call() {
            System.out.println(getCallerClassName(this.getClass()));
        }
    }

    /**
     * Searches the current threads stacktrace for the class that called the given class. Returns {@code null} if the
     * calling class could not be found.
     * 
     * @param clazz
     *            the class that has been called
     * 
     * @return the caller that called the class or {@code null}
     */
    public static String getCallerClassName(final Class<?> clazz) {
        final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        final String className = clazz.getName();
        boolean classFound = false;
        for (int i = 1; i < stackTrace.length; i++) {
            final StackTraceElement element = stackTrace[i];
            final String callerClassName = element.getClassName();
            // check if class name is the requested class
            if (callerClassName.equals(className)) classFound = true;
            else if (classFound) return callerClassName;
        }
        return null;
    }

}
benez
  • 1,856
  • 22
  • 28