/ HACK

getCaller() hack

As developers, we should only call public APIs. However, the Java language cannot differentiate between public API and private stuff: as soon as a class and one of its method is public, we can reference the former and call the later. Therefore, we are exposed to the Dark Side of the Force, and sometimes tempted to use it.

A good example of this terrible temptation is the sun.reflect.Reflection.getCaller(int) method. As its name implies, this evil piece returns which class called your current code, letting you tailor your code behavior depending on the calling class. The Dark Side can be seductive indeed!

This is a devious hack, it should only be used if you know what you’re doing.

This way, we can check whether some caller has some property and let it invoke our stuff or not accordingly. For example, let’s design a code piece that can be invoked only from "privileged" code. Such privileges include: be a specific class, come from a specific package, be annotated with a specific annotation, etc. The following code uses the third option.

import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Retention(RUNTIME)
@Target(TYPE)
public @interface Privileged {}

Now, checking for this annotation’s existence in the calling code is achieved like this:

public class Restricted {
    public Restricted() {
    int i = 2;
    while (true) {
        Class<?> caller = Reflection.getCallerClass(i++);
        if (caller == null) {
            throw new SecurityException();
        }
        if (caller.getAnnotation(Privileged.class) != null) {
            break;
        }
    }
}
  1. The int parameter in the getCallerClass() method refers to the number of frames up the calling stack. Index 0 is Reflection.getCallerClass() itself, while index 1 is our restricted constructor, so that real work should begin at 2.
  2. The algorithm is to search up the stack until either the whole stack has been browsed and no privileged code has been found or stop when the matching annotation is found. In the former case, throw an exception, in the latter, do the job as expected.
  3. Of course, this algorithm shouldn’t be hard-coded but factorized in a aspect and injected at some determined pointcut (perhaps a @Restricted annotation?)

Ok, but what use-case does this solve? Well, I’ve always thought about what requirements Hibernate (or JPA) put on your design. One of such requirement is to provide a no-arg constructor, for the framework to instantiate. After that, it can use setter or inject attributes directly. Unfortunately, this conflicts with my desire to provide immutable objects that have all mandatory parameters in the constructor. A basic solution would be to provide both constructors but document them accordingly (/** Do not use: this one is for Hibernate. */). However, to really enforce this rule, you’d probably need to use getCallerClass() with a check on an org.hibernate package.

Beware that Oracle intended to remove this method in the next JDK. Again, do not use this at home, but it opens some interesting perspectives, doesn’t it? […​evil laugh…​]

Download the above code in IntelliJ/Maven format here.

Nicolas Fränkel

Nicolas Fränkel

Developer Advocate with 15+ years experience consulting for many different customers, in a wide range of contexts (such as telecoms, banking, insurances, large retail and public sector). Usually working on Java/Java EE and Spring technologies, but with focused interests like Rich Internet Applications, Testing, CI/CD and DevOps. Also double as a trainer and triples as a book author.

Read More
getCaller() hack
Share this