While playing around with Java stack traces today, I noticed something that I had never noticed before: lambda frames do not show up in stack traces!

For instance, consider the following pre-Java 8 example:

public class VisibleLambda {
  public static void main(String[] args) {
    foo(new Runnable() {
      public void run() {
        bar();
      }
    });
  }

  static void foo(Runnable lambda) {
    lambda.run();
  }

  static void bar() {
    throw new RuntimeException();
  }
}

In the main method, we create an anonymous inner class that implements Runnable and calls bar when run.

The resulting output for this program is as expected:

Exception in thread "main" java.lang.RuntimeException
        at VisibleLambda.bar(VisibleLambda.java:15)
        at VisibleLambda$1.run(VisibleLambda.java:5)
        at VisibleLambda.foo(VisibleLambda.java:11)
        at VisibleLambda.main(VisibleLambda.java:3)

Checks out: main calls foo, which then calls the Runnable::run method (here implemented by a class automatically named VisibleLambda$1), which then calls bar. So far, so good, and this matched my understanding of how this works together.

But consider now what happens when we switch to using a Java 8 lambda:

public class HiddenLambda {
  public static void main(String[] args) {
    foo(() -> bar());
  }

  static void foo(Runnable lambda) {
    lambda.run();
  }

  static void bar() {
    throw new RuntimeException();
  }
}

…​and the output is:

Exception in thread "main" java.lang.RuntimeException
        at HiddenLambda.bar(HiddenLambda.java:11)
        at HiddenLambda.lambda$main$0(HiddenLambda.java:3)
        at HiddenLambda.foo(HiddenLambda.java:7)
        at HiddenLambda.main(HiddenLambda.java:3)

Did you see it? Two interesting things happened here. One is that a new method named lambda$main$0 appeared in the HiddenLambda class. The other is that our Runnable is gone — nowhere on the stack we can see a run method being called, although it is definitely there.

So what’s the lambda$main$0? Using javap -p -c HiddenLambda we can see that it contains the code to our little lambda:

public class HiddenLambda {
  private static void lambda$main$0();
    Code:
       0: invokestatic  #7                  // Method bar:()V
       3: return
}

So where’s our Runnable? Here the plot thickens. It turns out that Java 8 introduced a new, internal-only magic annotation called @java.lang.invoke.LambdaForm$Hidden. This annotation is used in many methods used by lambdas, and in the lambdas themselves.

When the JVM is reading Java classes containing methods that are tagged with this magic annotation, it sets an internal flag on each method predictably called _hidden. Then, quite simply, in the code implementing Throwable::fill_in_stack_trace this flag is checked and any methods tagged with it are not included in the stack trace.

After reading a bit more of the source code, I’ve discovered that behavior can be controlled with command-line options: -XX:+UnlockDiagnosticVMOptions -XX:+ShowHiddenFrames.

So finally, here it is in action:

$ java -XX:+UnlockDiagnosticVMOptions -XX:+ShowHiddenFrames HiddenLambda
Exception in thread "main" java.lang.RuntimeException
        at HiddenLambda.bar(HiddenLambda.java:11)
        at HiddenLambda.lambda$main$0(HiddenLambda.java:3)
        at HiddenLambda$$Lambda$1/791452441.run(<Unknown>:1000000)
        at HiddenLambda.foo(HiddenLambda.java:7)
        at HiddenLambda.main(HiddenLambda.java:3)