Course topics

By WebNest Studio Engineering Team

Java - Core Tutorial

Lambda Expressions

A lambda expression is a compact way to represent an anonymous function — a block of code with parameters that can be passed around like a value. Java 8 introduced lambdas primarily to reduce the boilerplate of anonymous inner classes when implementing interfaces that have exactly one method, such as event listeners, comparators, and runnables.

Instead of writing a full class definition just to supply one piece of behavior, a lambda lets you write only the parameters and the logic: (parameters) -> expression or (parameters) -> { statements; }. The compiler infers the target type from the context, most often a functional interface.

Why it matters

Lambdas changed how Java is written. Streams, comparators, event handlers, thread tasks and most of the Spring API expect you to pass behaviour as a short function. Code written since Java 8 is hard to read, and harder to write, without being comfortable with the -> syntax.

Lambda Syntax Forms

Java accepts several equivalent shapes depending on how much you want to write explicitly. All of the following are valid ways to express "take two ints and return their sum":

  • (int a, int b) -> a + b — explicit parameter types, single expression.
  • (a, b) -> a + b — inferred parameter types (the compiler figures out int from context).
  • (a, b) -> { return a + b; } — block body with an explicit return statement.
  • x -> x * x — parentheses can be dropped for exactly one inferred-type parameter.

Replacing Anonymous Inner Classes

Before Java 8, passing behavior meant writing an anonymous inner class that implemented an interface. A lambda produces the same effect with far less ceremony, because the compiler already knows which single method is being implemented from the target functional interface — you only supply the parameter list and body, not the interface name or method signature again.

Variable Capture: Effectively Final

A lambda can read local variables from its enclosing scope, but only if those variables are "effectively final" — meaning they are never reassigned after initialization, even if not explicitly marked final. This restriction exists because the lambda may outlive the method call (for example, if stored and invoked later), so Java captures the variable's value rather than allowing it to change underneath the lambda.

Example

Replacing an anonymous Runnable and Comparator with lambdas

Java
import java.util.Arrays;
import java.util.List;

public class LambdaDemo {
    public static void main(String[] args) {
        Runnable task = () -> System.out.println("Running via lambda");
        task.run();

        List<String> names = Arrays.asList("Banana", "apple", "Cherry");
        names.sort((a, b) -> a.compareToIgnoreCase(b));
        System.out.println(names);

        int factor = 3; // effectively final, safe to capture
        Runnable multiplier = () -> System.out.println("Factor is " + factor);
        multiplier.run();
    }
}
Output
Running via lambda
[apple, Banana, Cherry]
Factor is 3

Common Mistakes

  • Trying to reassign a local variable after capturing it in a lambda — this fails to compile because the variable must be effectively final.
  • Adding a "return" keyword inside a single-expression lambda body, such as writing "x -> return x * x;", which is invalid syntax.
  • Assuming a lambda can implement an interface with more than one abstract method — lambdas only target functional (single abstract method) interfaces.

Exercise

Sort the words by length, shortest first, by passing a lambda to sort.

Expected output: [fig, kiwi, mango]

Java · starter code
import java.util.ArrayList;
import java.util.List;

public class SortByLength {
    public static void main(String[] args) {
        List<String> words = new ArrayList<>(List.of("mango", "fig", "kiwi"));

        // TODO: sort the list by word length using a lambda

        System.out.println(words);
    }
}
Show hints
  • words.sort(...) takes a Comparator, which has one method with two parameters.
  • The lambda (a, b) -> Integer.compare(a.length(), b.length()) compares two words by length.
Show solution
Java · solution
import java.util.ArrayList;
import java.util.List;

public class SortByLength {
    public static void main(String[] args) {
        List<String> words = new ArrayList<>(List.of("mango", "fig", "kiwi"));

        words.sort((a, b) -> Integer.compare(a.length(), b.length()));

        System.out.println(words); // [fig, kiwi, mango]
    }
}

Quick quiz

1. Which of these is a valid lambda for a method that takes two ints and returns their sum?

  • A. (a, b) -> return a + b
  • B. (a, b) => a + b
  • C. a, b -> a + b
  • D. (a, b) -> a + b
Show answer

D. Java uses ->. Several parameters need parentheses, and return is only allowed inside a block with braces.

2. A lambda uses a local variable from the enclosing method. What must be true of that variable?

  • A. It must be final or effectively final
  • B. It must be static
  • C. It must be a primitive
  • D. It must be declared inside the lambda
Show answer

A. The variable may not be reassigned after it is first given a value.

3. What can a lambda expression be assigned to?

  • A. Any class
  • B. A functional interface, which has exactly one abstract method
  • C. Any interface
  • D. Only Runnable
Show answer

B. The lambda supplies the body of that one abstract method.

Interview questions

What is a lambda expression?

Show answer

A lambda is a short way to write an implementation of a functional interface: a parameter list, an arrow and a body. It lets you pass behaviour to a method as an argument, for example a comparison to sort or a task to a thread, without declaring a class.

How does a lambda differ from an anonymous inner class?

Show answer

An anonymous class can implement an interface with several methods and can have its own fields; a lambda can only implement a functional interface and has no state of its own. Inside an anonymous class, this refers to the anonymous object, while inside a lambda it refers to the enclosing object. The compiler also produces no separate class file for a lambda.

Why must local variables captured by a lambda be effectively final?

Show answer

The lambda may run later, possibly on another thread, after the method that created it has returned and its local variables are gone. Java therefore copies the value into the lambda. If the variable could change afterwards, the copy and the original would disagree, so the language forbids reassigning it.

Key Points to Remember

  • A lambda is (parameters) -> expression or (parameters) -> { statements; }, targeting a functional interface.
  • Lambdas replace anonymous inner classes for single-method interfaces with far less boilerplate.
  • Captured local variables must be effectively final — never reassigned after their initial value.

Practice in the playground

Write and run your own code for this topic, or start from the exercise or an example. Runs plain Java 17. Frameworks such as Spring, and databases, are not available here. Your code for each topic is kept in this browser.

Run the examples

Change an input, predict the result, then compare it with the output. Explain why the result changes.

WebNest Studio

Written by

WebNest Studio Engineering Team

Practical lessons from developers working with Java, Python, React, databases and production systems.

Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering