How Lambdas Are Compiled

Naveen AnamaneniUpdated · 5 min read

A lambda looks like a short form of an anonymous class, but the compiler treats it very differently. An anonymous class becomes a separate .class file. A lambda becomes a private method in the same class, plus a single invokedynamic instruction that asks the JVM, at runtime, to create an object implementing the functional interface. The JVM does this with a helper called LambdaMetafactory. In this lesson, you will look inside the compiled code to see exactly what happens, and learn why this design makes lambdas efficient.

Example: One Lambda and One Anonymous Class

Let's compile a class that contains one of each and see what files appear.

Hello.java▶ Try it
import java.util.function.Function;
 
public class Hello {
    public static void main(String[] args) {
        Function<String, String> greet = name -> "Hi " + name;
        System.out.println(greet.apply("Navika"));
        Runnable r = new Runnable() {
            public void run() { }
        };
    }
}
Output
Hi Navika
Terminal
javac Hello.java
ls *.class
Terminal output
Hello$1.class
Hello.class

Explanation: The anonymous Runnable became its own file, Hello$1.class. The lambda produced no extra class file at all.

Step 1: The Lambda Body Becomes a Private Method

The javap tool shows the compiled bytecode. Running javap -c -p Hello.class prints:

Terminal output
Compiled from "Hello.java"
public class Hello {
  public Hello();
    Code:
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
 
  public static void main(java.lang.String[]);
    Code:
         0: invokedynamic #7,  0              // InvokeDynamic #0:apply:()Ljava/util/function/Function;
         5: astore_1
         6: getstatic     #11                 // Field java/lang/System.out:Ljava/io/PrintStream;
         9: aload_1
        10: ldc           #17                 // String Navika
        12: invokeinterface #19,  2           // InterfaceMethod java/util/function/Function.apply:(Ljava/lang/Object;)Ljava/lang/Object;
        17: checkcast     #24                 // class java/lang/String
        20: invokevirtual #26                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        23: new           #32                 // class Hello$1
        26: dup
        27: invokespecial #34                 // Method Hello$1."<init>":()V
        30: astore_2
        31: return
 
  private static java.lang.String lambda$main$0(java.lang.String);
    Code:
         0: aload_0
         1: invokedynamic #35,  0             // InvokeDynamic #1:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
         6: areturn
}

Explanation:

  • The lambda's body, "Hi " + name, was moved into a new private static method named lambda$main$0. (It's static because the lambda doesn't use this; a lambda that uses instance fields becomes an instance method.)
  • Where the lambda was written, main now has an invokedynamic instruction (at 0). It returns a Function object.
  • The anonymous class is created the old way, with new Hello$1.

Step 2: invokedynamic and LambdaMetafactory

invokedynamic is a JVM instruction, added in Java 7, whose target is decided at runtime the first time it runs. Each invokedynamic points to a bootstrap method. For lambdas, that bootstrap method is LambdaMetafactory.metafactory. javap -v shows it in the class file's BootstrapMethods table, together with the method handle for lambda$main$0.

The first time the instruction runs:

  1. The JVM calls LambdaMetafactory.metafactory, passing the functional interface (Function), its method name (apply) and a handle to lambda$main$0.
  2. LambdaMetafactory generates, in memory, a small hidden class that implements Function and whose apply calls lambda$main$0.
  3. The result is linked to the call site, so later executions skip all of this and simply return the object. For a lambda that captures no variables, it's the same object every time.
javac writes a private method and an invokedynamic; the JVM builds the class at first use

You can ask the JVM to save those hidden classes to disk to see them:

Terminal
java -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles=true Hello
ls DUMP_LAMBDA_PROXY_CLASS_FILES
Terminal output
Hi Navika
Hello$$Lambda.0x00003fc001040210.class

Explanation: The class name includes a number that changes from run to run, because the class is generated while the program runs, not by javac.

Why This Design Is Better

  • Smaller programs: no class file per lambda, so JAR files are smaller and load faster.
  • Lazy: the implementation class is created only if the lambda is actually used.
  • Flexible: because the strategy lives in the JVM, not in your compiled code, future JVMs can generate lambdas in better ways without recompiling.
  • Cheap reuse: non-capturing lambdas are created once and shared.

Common Mistakes

Saying "a lambda is just an anonymous class". It compiles to a private method and an invokedynamic, with no class file.

Thinking lambdas are slow because of the runtime setup. The setup happens once per call site; later calls are as fast as a normal interface call.

Interview Corner

How are lambda expressions compiled in Java?

The compiler moves the lambda's body into a private synthetic method, such as lambda$main$0, and replaces the lambda expression with an invokedynamic instruction whose bootstrap method is LambdaMetafactory.metafactory. On first execution, the JVM calls the bootstrap method, which generates a hidden class implementing the functional interface that delegates to the synthetic method, and links the call site. Later executions reuse the linked call site. Unlike anonymous classes, no separate class file is produced at compile time.

Practice

  1. Easy: Compile a class with three lambdas and run javap -p on it. How many lambda$ methods do you see?
  2. Harder: Write one lambda that uses an instance field and one that uses nothing. Use javap -p to check which lambda$ method is static and which isn't, and explain why.

Quick check: How lambdas are compiled

4 questions
1Does javac create a separate .class file for each lambda?
2Where does the lambda's body go?
3Which instruction creates the lambda object?
4Which class's method is the bootstrap for lambdas?

Key Takeaways

Was this tutorial helpful?

Lesson 311 of 482