An anonymous inner class (AIC) can be used to create a subclass of an abstract class or a concrete class. An AIC can also provide a concrete implementation of an interface, including the addition of state (fields). An instance of an AIC can be referred to using this in its method bodies, so further methods can be called on it, its state can be mutated over time, etc. None of these apply to lambdas.

I'd guess that the majority of uses of AICs were to provide stateless implementations of single functions and so can be replaced with lambda expressions, but there are other uses of AICs for which lambdas cannot be used. AICs are here to stay.

UPDATE

Another difference between AICs and lambda expressions is that AICs introduce a new scope. That is, names are resolved from the AIC's superclasses and interfaces and can shadow names that occur in the lexically enclosing environment. For lambdas, all names are resolved lexically.

Answer from Stuart Marks on Stack Overflow
Top answer
1 of 10
120

An anonymous inner class (AIC) can be used to create a subclass of an abstract class or a concrete class. An AIC can also provide a concrete implementation of an interface, including the addition of state (fields). An instance of an AIC can be referred to using this in its method bodies, so further methods can be called on it, its state can be mutated over time, etc. None of these apply to lambdas.

I'd guess that the majority of uses of AICs were to provide stateless implementations of single functions and so can be replaced with lambda expressions, but there are other uses of AICs for which lambdas cannot be used. AICs are here to stay.

UPDATE

Another difference between AICs and lambda expressions is that AICs introduce a new scope. That is, names are resolved from the AIC's superclasses and interfaces and can shadow names that occur in the lexically enclosing environment. For lambdas, all names are resolved lexically.

2 of 10
67

Lambdas though a great feature, will only work with SAM types. That is, interfaces with only a single abstract method. It would fail as soon as your interface contains more than 1 abstract method. That is where anonymous classes will be useful.

So, no we cannot just ignore anonymous classes. And just FYI, your sort() method can be more simplified, by skipping the type declaration for p1 and p2:

Collections.sort(personList, (p1, p2) -> p1.firstName.compareTo(p2.firstName));

You can also use method reference here. Either you add a compareByFirstName() method in Person class, and use:

Collections.sort(personList, Person::compareByFirstName);

or, add a getter for firstName, directly get the Comparator from Comparator.comparing() method:

Collections.sort(personList, Comparator.comparing(Person::getFirstName));
Discussions

performance - Big execution time difference between java Lambda vs Anonymous class - Stack Overflow
I was curious about performance of creation of java8 lambda instances against the same anonymous class. (Measurement performed on win32 java build 1.8.0-ea-b106). I've created very simple example and More on stackoverflow.com
🌐 stackoverflow.com
java - Lambda vs anonymous inner class performance: reducing the load on the ClassLoader? - Stack Overflow
I would like to know how big of a benefit lambdas have in Java 8. I agree that it might be more readable sometimes to use lambdas, but does it have really such of a big impact on the performance si... More on stackoverflow.com
🌐 stackoverflow.com
July 15, 2014
algorithm - Java lambdas 20 times slower than anonymous classes - Stack Overflow
I don't know what LeetCode does to achieve this weird performance, so the question is this: what it could be doing to make it that bad? ... If the code is executed only once (for the examples on leetcode) the decreasing execution time might be related due to the fact that the lambda bytecode is generated at runtime. Whereas your anonymous comparator class ... More on stackoverflow.com
🌐 stackoverflow.com
performance - Are there plans to make lambda expressions in Java more performant than anonymous classes? - Stack Overflow
When I first heard about lambdas in Java I thought that this was already the case. However, I later read that this study which says that this is not the true. It says that there are possibilities of More on stackoverflow.com
🌐 stackoverflow.com
🌐
Medium
medium.com › javarevisited › experienced-developers-use-these-quirks-to-create-better-java-lambdas-4ae656148274
Java 8 Lambdas vs Anonymous Classes Performance | Javarevisited
February 23, 2022 - Lambdas, with the help of indy, are converted to function objects. IDE suggests lambda instead of anonymous classes. Even so, Java runtime doesn't just convert lambda to an anonymous class.
🌐
Baeldung
baeldung.com › home › java › core java › lambda expression vs. anonymous inner class
Lambda Expression vs. Anonymous Inner Class | Baeldung
January 8, 2024 - This is because an anonymous class leads to an extra class file on the compilation, which takes additional time during class loading and verification during runtime. The performance of lambda expressions is better because the invokedynamic ...
Top answer
1 of 1
30

UPDATE

A few comments wondering if my benchmark at the bottom was flawed - after introducing a lot of randomness (to prevent the JIT from optimising too much stuff), I still get similar results so I tend to think it is ok.

In the meantime, I have come across this presentation by the lambda implementation team. Page 16 shows some performance figures: inner classes and closures have similar performance / non-capturing lambda are up to 5x times faster.

And @StuartMarks posted this JVMLS 2013 talk from Sergey Kuksenko on lambda performance. The bottom line is that post JIT compilation, lambdas and anonymous classes perform similarly on current Hostpot JVM implementations.


YOUR BENCHMARK

I have also run your test, as you posted it. The problem is that it runs for as little as 20 ms for the first method and 2 ms for the second. Although that is a 10:1 ratio, it is in no way representative because the measurement time is way too small.

I have then taken modified your test to allow for more JIT warmup and I get similar results as with jmh (i.e. no difference between anonymous class and lambda).

public class Main {

    static interface ICallback {
        void payload();
    }
    static void measureAnonymousClass() {
        final int arr[] = {0};
        ICallback clb = new ICallback() {
            @Override
            public void payload() {
                arr[0]++;
            }
        };
        clb.payload();
    }
    static void measureLambda() {
        final int arr[] = {0};
        ICallback clb = () -> {
            arr[0]++;
        };
        clb.payload();
    }
    static void runTimed(String message, Runnable act) {
        long start = System.nanoTime();
        for (int i = 0; i < 10_000_000; i++) {
            act.run();
        }
        long end = System.nanoTime();
        System.out.println(message + ":" + (end - start));
    }
    public static void main(String[] args) {
        runTimed("as lambdas", Main::measureLambda);
        runTimed("anonymous class", Main::measureAnonymousClass);
        runTimed("as lambdas", Main::measureLambda);
        runTimed("anonymous class", Main::measureAnonymousClass);
        runTimed("as lambdas", Main::measureLambda);
        runTimed("anonymous class", Main::measureAnonymousClass);
        runTimed("as lambdas", Main::measureLambda);
        runTimed("anonymous class", Main::measureAnonymousClass);
    }
}

The last run takes about 28 seconds for both methods.


JMH MICRO BENCHMARK

I have run the same test with jmh and the bottom line is that the four methods take as much time as the equivalent:

void baseline() {
    arr[0]++;
}

In other words, the JIT inlines both the anonymous class and the lambda and they take exactly the same time.

Results summary:

Benchmark                Mean    Mean error    Units
empty_method             1.104        0.043  nsec/op
baseline                 2.105        0.038  nsec/op
anonymousWithArgs        2.107        0.028  nsec/op
anonymousWithoutArgs     2.120        0.044  nsec/op
lambdaWithArgs           2.116        0.027  nsec/op
lambdaWithoutArgs        2.103        0.017  nsec/op
🌐
Medium
medium.com › @nagarjun_nagesh › lambda-functions-vs-anonymous-inner-classes-in-java-80893c2214d5
Lambda Functions vs. Anonymous Inner Classes in Java | by Nagarjun (Arjun) Nagesh | Medium
February 23, 2024 - Lambda functions are implemented using invokedynamic bytecode instructions, which offer better performance compared to the traditional approach of using anonymous inner classes. Additionally, lambda functions have less overhead in terms of memory ...
Find elsewhere
🌐
NashTech Blog
blog.nashtechglobal.com › home › java lambda vs anonymous class
Java Lambda Vs Anonymous Class - NashTech Blog
March 11, 2022 - The Performance of the lambda expression is better as it is pure compile-time activity and doesn’t incur extra cost during runtime. However, the Performance of the anonymous inner class is lower as requires class loading at runtime.
🌐
E Innovations
eginnovations.com › blog › demystifying-java-lambda-expressions
Demystifying Java Lambda Expressions | eG Innovations
February 29, 2024 - As a result, the more anonymous inner classes you have, the longer it may take for your application to start up, potentially leading to a less responsive user experience. Java lambda expressions result in lesser number of classes and therefore ...
🌐
Prgrmmng
prgrmmng.com › home › series › java lambdas and functional interfaces › lambdas vs anonymous classes: performance, readability, and maintainability
Lambdas vs Anonymous Classes: Performance, Readability, and Maintainability | prgrmmng.com
August 11, 2025 - Use lambdas for clean, expressive, and performant code when implementing functional interfaces. Prefer anonymous classes for more complex, stateful implementations or when working with legacy code.
🌐
Medium
medium.com › @ntiinsd › java-lambdas-vs-anonymous-classes-which-wins-in-2025-359677efd659
Java Lambdas vs. Anonymous Classes: Which Wins in 2025? | by inside Nikita's Mind | Medium
June 28, 2025 - They shine in stream operations and event handling. Anonymous Classes are inner classes without a name, used for implementing interfaces or extending classes, offering flexibility but often verbose syntax.
Top answer
1 of 1
85

You are obviously encountering the first-time initialization overhead of lambda expressions. As already mentioned in the comments, the classes for lambda expressions are generated at runtime rather than being loaded from your class path.

However, being generated isn’t the cause for the slowdown. After all, generating a class having a simple structure can be even faster than loading the same bytes from an external source. And the inner class has to be loaded too. But when the application hasn’t used lambda expressions before¹, even the framework for generating the lambda classes has to be loaded (Oracle’s current implementation uses ASM under the hood). This is the actual cause of the slowdown, loading and initialization of a dozen internally used classes, not the lambda expression itself².

You can easily verify this. In your current code using lambda expressions, you have two identical expressions (i1, i2) -> Integer.compare(i1.start, i2.start). The current implementation doesn’t recognize this (actually, the compiler doesn’t provide a hint neither). So here, two lambda instances, having even different classes, are generated. You can refactor the code to have only one comparator, similar to your inner class variant:

final Comparator<? super Interval> comparator
  = (i1, i2) -> Integer.compare(i1.start, i2.start);
int start = Collections.binarySearch(intervals, newInterval, comparator);
int skip = start >= 0 ? start : -start - 1;
int end = Collections.binarySearch(intervals.subList(skip, intervals.size()),
                                   new Interval(newInterval.end, 0),
                                   comparator);

You won’t notice any significant performance difference, as it’s not the number of lambda expressions that matters, but just the class loading and initialization of the framework, which happens exactly once.

You can even max it out by inserting additional lambda expressions like

final Comparator<? super Interval> comparator1
    = (i1, i2) -> Integer.compare(i1.start, i2.start);
final Comparator<? super Interval> comparator2
    = (i1, i2) -> Integer.compare(i1.start, i2.start);
final Comparator<? super Interval> comparator3
    = (i1, i2) -> Integer.compare(i1.start, i2.start);
final Comparator<? super Interval> comparator4
    = (i1, i2) -> Integer.compare(i1.start, i2.start);
final Comparator<? super Interval> comparator5
    = (i1, i2) -> Integer.compare(i1.start, i2.start);

without seeing any slowdown. It’s really the initial overhead of the very first lambda expression of the entire runtime you are noticing here. Since Leetcode itself apparently doesn’t use lambda expressions before entering your code, whose execution time gets measured, this overhead adds to your execution time here.

See also “How will Java lambda functions be compiled?” and “Does a lambda expression create an object on the heap every time it's executed?”

¹ This implies that JDK code that will be executed before handing control over to your application doesn’t use lambda expressions itself. Since this code stems from times before the introduction of lambda expressions, this is usually the case. With newer JDKs, modular software will be initialized by different, newer code, which seems to use lambda expressions, so the initialization of the runtime facility can’t be measured within the application anymore in these setups.

² The initialization time has been reduced significantly in newer JDKs. There are different possible causes, general performance improvements, dedicated lambda optimizations, or both. Improving initialization time in general, is an issue that the JDK developers did not forget.

🌐
Medium
mcvalls.medium.com › java-lambda-expressions-and-anonymous-classes-3b49489388b5
Java: Lambda Expressions and Anonymous Classes | by Marcelo Valls | Medium
February 22, 2022 - I’ve once heard a dev talking about “some functional programming engine” working behind the scenes (?), or saying things like “Lambda expressions perform terribly bad” and so on… but actually, they are just anonymous classes implemented in the place. The key about Lambda Expressions is that they represent algorithms. Even if they are Anonymous Classes and can be referenced as simple Java Objects, they are still representing algorithms, and it allows us to think about our code in a more functional way.
🌐
Stack Overflow
stackoverflow.com › questions › 27409330 › are-there-plans-to-make-lambda-expressions-in-java-more-performant-than-anonymou
performance - Are there plans to make lambda expressions in Java more performant than anonymous classes? - Stack Overflow
It's almost certainly better now than it was as of that presentation. It might have passed the point where it beats anonymous classes, and it might not have; I don't know of up-to-date benchmarks.
🌐
O'Reilly
oreilly.com › library › view › refactoring-to-modern › 9780134653617 › RFMJ_03_01.html
3.1 Performance of Anonymous Inner Classes versus Lambda Expressions - Refactoring to Modern Java [Video]
1.1 Why Java 8? 4m 42s1.2 Prerequisites · 4m 55s1.3 Project Code · 1m 43s · 2.1 Lambda Expressions · 22m 1s2.2 Collections and the Streams API · 28m 25s2.3 Getting to Grips with Optional · 14m 7s · 3.1 Performance of Anonymous Inner Classes versus Lambda Expressions ·
🌐
Javaplanet
javaplanet.io › home › lambda expressions › performance considerations with lambda expressions
Performance Considerations with Lambda Expressions -
September 6, 2025 - One of the main concerns regarding the performance of lambda expressions is the object creation overhead. Lambda expressions are compiled into anonymous classes under the hood, which can lead to additional memory allocation and runtime overhead. Anonymous Class Creation: When a lambda expression ...
🌐
DZone
dzone.com › coding › frameworks › how lambdas and anonymous inner classes work
How Lambdas And Anonymous Inner Classes Work
February 8, 2017 - Lambdas implement a functional interface. Anonymous Inner Classes can extend a class or implement an interface with any number of methods.
Top answer
1 of 3
20

In many scenarios, I think lambda and method-reference is equivalent. But the lambda will wrap the invocation target by the declaring interface type.

For example

public class InvokeTest {

    private static void invoke(final Runnable r) {
        r.run();
    }

    private static void target() {
        new Exception().printStackTrace();
    }

    @Test
    public void lambda() throws Exception {
        invoke(() -> target());
    }

    @Test
    public void methodReference() throws Exception {
        invoke(InvokeTest::target);
    }
}

You will see the console output the stacktrace.

In lambda(), the method calling target() is lambda$lambda$0(InvokeTest.java:20), which has traceable line info. Obviously, that is the lambda you write, the compiler generates an anonymous method for you. And then, the caller of the of the lambda method is something like InvokeTest$$Lambda$2/1617791695.run(Unknown Source), that is the invokedynamic call in JVM, it means the call is linked to the generated method.

In methodReference(), the method calling target() is directly the InvokeTest$$Lambda$1/758529971.run(Unknown Source), it means the call is directly linked to the InvokeTest::target method.

Conclusion

Above all, compare to method-reference, using lambda expression will only cause one more method call to the generating method from lambda.

2 of 3
38

It's all about the metafactory

First, most method references do not need desugaring by the lambda metafactory, they are simply used as the reference method. Under the section "Lambda body sugaring" of the Translation of Lambda Expressions ("TLE") article:

All things being equal, private methods are preferable to nonprivate, static methods preferable to instance methods, it is best if lambda bodies are desugared into in the innermost class in which the lambda expression appears, signatures should match the body signature of the lambda, extra arguments should be prepended on the front of the argument list for captured values, and would not desugar method references at all. However, there are exception cases where we may have to deviate from this baseline strategy.

This is further highlighted further down in TLE's "The Lambda Metafactory":

metaFactory(MethodHandles.Lookup caller, // provided by VM
            String invokedName,          // provided by VM
            MethodType invokedType,      // provided by VM
            MethodHandle descriptor,     // lambda descriptor
            MethodHandle impl)           // lambda body

The impl argument identifies the lambda method, either a desugared lambda body or the method named in a method reference.

A static (Integer::sum) or unbounded instance method (Integer::intValue) references are the 'simplest' or the most 'convenient', in the sense that they can be optimally handled by a 'fast-path' metafactory variant without the desugaring. This advantage is helpfully pointed out in TLE's "Metafactory variants":

By eliminating arguments where they are not needed, classfiles become smaller. And the fast path option lowers the bar for the VM to intrinsify the lambda conversion operation, enabling it to be treated as a "boxing" operation and faciliating unbox optimizations.

Naturally, an instance-capturing method reference (obj::myMethod) needs to provide the bounded instance as an argument to the method handle for invocation, which may mean the need of desugaring using 'bridge' methods.

Conclusion

I'm not exactly sure what is the lambda 'wrapper' you are hinting at, but even though the ultimate result of using your user-defined lambdas or method references are the same, the way that is reached seems to be quite different, and can be different in the future if that's not the case now. Hence, I suppose it's more likely than not that method references can be handled in a more optimal way by the metafactory.

🌐
Medium
medium.com › @damian.kolasa › performance-implications-of-lambdas-and-method-references-when-mapping-a-stream-in-java-79f6e2da6806
Performance implications of lambdas and method references when mapping a stream in Java | by Damian Kolasa | Medium
June 13, 2018 - Or we can do a performance hack and remove one of the mapping functions: Lambda expression and more coarse grained mapping. We can ditch lambdas altogether and just create a anonymous instance of a required interface. Of course we know that creating a class instance (our implementation) every time we need to map a Person to a City is a bit of overkill (eden space problems).
🌐
Oracle
oracle.com › technetwork › java › jvmls2013kuksen-2014088.pdf pdf
JDK 8: Lambda Performance study Sergey Kuksenko
java.lang.Object ::<init > (1 bytes) Slide 32/55. Capture: lambda slow warmup · Main culprits: jsr292 LF implementation · layer of LF’s generated methods · HotSpot (interpreter) calling a method is hard (even simple delegating · methods) Slide 33/55. Capture: time-to-performance · extra invocations for anonymous ...