How is a Java lambda compiled and instantiated at runtime, and how does that differ from an anonymous class?
answer
- Anonymous class = real .class file at compile time
- Lambda = invokedynamic + LambdaMetafactory bootstrap
- Body lifted into a private synthetic method
- Implementation class synthesized at runtime, then cached
- Non-capturing lambda may be a shared instance
basics
~20 sAn anonymous class becomes its own .class file created at compile time. A lambda does not — the compiler emits an invokedynamic instruction, and at first run a bootstrap method (LambdaMetafactory) builds the implementation class on the fly.
solid answer
~50 sFor an anonymous class, `javac` generates a separate `.class` file (like `Outer$1.class`) at compile time, and `new` allocates one instance each time. A lambda is handled differently: the compiler puts the lambda body into a private synthetic method of the enclosing class and, at the call site, emits an `invokedynamic` bytecode instruction. The first time that line executes, the JVM calls a *bootstrap method* — `LambdaMetafactory.metafactory` — which dynamically synthesizes a class implementing the functional interface and returns a `CallSite` whose target produces the instance. The result is cached, so subsequent executions reuse it. This deferred, runtime synthesis means lambdas avoid a class-file-per-lambda explosion, give the JVM freedom to optimize the strategy over time, and — for stateless lambdas — let the platform return the same singleton instance instead of allocating a new object on every evaluation.
code
java · 14 lines// Source
Runnable r = () -> System.out.println("hi");
// Conceptually, javac produces:
// * a private synthetic method holding the body:
// private static void lambda$main$0() { System.out.println("hi"); }
// * an invokedynamic at the use site, bootstrapped by:
// LambdaMetafactory.metafactory(
// lookup, "run", MethodType (()->Runnable),
// MethodType (()->void), // erased signature
// MethodHandle -> lambda$main$0,
// MethodType (()->void)) // instantiated signature
// On first execution the JVM synthesizes a class implementing Runnable
// and caches the resulting CallSite; no Outer$1.class is generated.go deeper
Knows a lambda is 'lighter' than an anonymous class and doesn't create a separate class file the same way.
Can state that lambdas use invokedynamic and a runtime factory while anonymous classes are real compile-time classes.
Explains the full pipeline: body lifted to a synthetic method, invokedynamic + LambdaMetafactory bootstrap, lazy synthesis, caching, and the non-capturing instance-sharing implication.
Reasons about the design intent — deferred strategy, smaller artifacts, future VM optimizations — and the consequences for identity, startup cost, and tooling.
## Background terms - **`javac`**: the Java source-to-bytecode compiler. Bytecode is the instruction set the JVM executes, stored in `.class` files. - **Functional interface**: an interface with exactly one abstract method (e.g. `Runnable.run`). A lambda is a value of such an interface. - **`invokedynamic`**: a JVM bytecode instruction (added in Java 7) that leaves the *exact method to call* unresolved at compile time; it is linked lazily on first execution by a **bootstrap method**. - **Bootstrap method**: a method the JVM calls once per `invokedynamic` call site to decide what that site should actually do, returning a `CallSite`. - **`LambdaMetafactory`**: a JDK class whose `metafactory` (or `altMetafactory`) is the bootstrap method for lambdas; it builds the class that implements the functional interface. ## How an anonymous class is compiled When you write `new Runnable() { public void run() { ... } }`, `javac` generates a **fully formed, named `.class` file at compile time** — e.g. `Outer$1.class`. Every time that `new` expression runs, the JVM allocates a fresh object of that class. The number of generated class files grows with the number of anonymous classes, which inflates the JAR and lengthens class loading. ## How a lambda is compiled 1. **Body lifted to a method.** `javac` moves the lambda's code into a *private, synthetic method* of the enclosing class (e.g. `lambda$main$0`). The lambda does not become its own class file at compile time. 2. **`invokedynamic` at the call site.** Where the lambda appears, the compiler emits an `invokedynamic` instruction whose bootstrap is `LambdaMetafactory.metafactory`, with static arguments describing the functional-interface type, the method signature, and a *method handle* to that lifted method. 3. **Runtime synthesis (deferred).** The *first* time control reaches that instruction, the JVM invokes the bootstrap. `LambdaMetafactory` **spins up an implementation class at runtime** (historically via `InnerClassLambdaMetafactory` generating bytecode in memory) that implements the functional interface and delegates to the lifted method handle. It returns a `CallSite`. 4. **Linked and cached.** The `CallSite`'s target is bound to the call site, so subsequent executions skip the bootstrap entirely and just run the linked logic. ## Why design it this way? - **No class-per-lambda at compile time.** The decision of *how* to materialize the lambda is deferred to runtime, so the compiled artifact stays small and the strategy can evolve without recompiling code. - **Strategy freedom.** Because only an `invokedynamic` + a metafactory contract is baked in, the JDK can change the synthesis approach (or a future VM could intern, specialize, or stack-allocate) transparently. - **Possible instance sharing.** For a **non-capturing** lambda (one that captures no enclosing state), the metafactory may return a *single shared instance* every time the expression evaluates — there is **no guarantee a new object is created per evaluation**. Capturing lambdas generally need a fresh instance to hold the captured values. Anonymous classes, by contrast, allocate a new object on every `new`. ## Identity caveat Because non-capturing lambdas may be cached, you must **not rely on lambda object identity** (`==`), and the runtime class name of a lambda is an unstable, synthetic name — never code against it. ## Summary | | Anonymous class | Lambda | |---|---|---| | Class file | one `.class` at compile time | none at compile time; synthesized at runtime | | Bytecode at use site | `new` of that class | `invokedynamic` → `LambdaMetafactory` | | Instance per evaluation | always a new object | possibly a shared instance (non-capturing) | | Linking | compile-time | lazy, on first execution, then cached |
- What is the role of the bootstrap method in lambda linkage?On the first execution of the `invokedynamic` call site, the JVM calls the bootstrap (`LambdaMetafactory.metafactory`), which synthesizes a class implementing the functional interface and returns a `CallSite`. That target is then linked and cached so later executions skip the bootstrap.
- Does the JVM guarantee a new instance for every lambda evaluation?No. For non-capturing lambdas the metafactory is permitted to return a shared, cached instance. Only capturing lambdas generally require a fresh object to hold the captured state.
saying these in an interview costs you the question
- Saying a lambda compiles to its own `.class` file like an anonymous class
- Claiming each lambda evaluation always allocates a new object
- Relying on the lambda's synthetic runtime class name or on `==` identity
- Thinking `invokedynamic` resolves the implementation at compile time