Java: Lambda Expressions

16 Jul 2026 5 min read
1
Lambda Expressions provide a concise way to represent anonymous functions in Java.

Introduced in Java 8, they eliminate much of the boilerplate required for anonymous classes and enable a functional programming style.

Lambda expressions are primarily used to implement functional interfaces and form the foundation of the Stream API, method references, and many modern Java APIs.

What is a Lambda Expression?

A lambda expression is an anonymous function that can be passed as data, assigned to variables, or returned from methods. It consists of a parameter list, an arrow operator (->), and a function body.
(parameters) -> expression

(parameters) -> {
    // Method body
}
Unlike regular methods, lambda expressions do not have a name, return type, or access modifiers.

Why Lambda Expressions?

Before Java 8, behavior was commonly passed using anonymous classes.
Runnable task = new Runnable() {

    @Override
    public void run() {
        System.out.println("Running");
    }
};
Using a lambda expression:
Runnable task =
        () -> System.out.println("Running");
Lambda expressions significantly reduce boilerplate while improving readability.

Lambda Syntax

A lambda expression consists of three parts.

1. Parameters
2. Arrow Operator (->)
3. Body
(a, b) -> a + b
If the body contains a single expression, its value is returned automatically.

No Parameter Lambda

Runnable task =
        () -> System.out.println("Hello");

Single Parameter Lambda

Parentheses may be omitted when there is a single parameter.
Consumer<String> consumer =
        name -> System.out.println(name);
Both forms are valid.
(name) -> System.out.println(name)
name -> System.out.println(name)

Multiple Parameters

Multiple parameters require parentheses.
BinaryOperator<Integer> add = (a, b) -> a + b;

Expression Body

Single-expression lambdas return the expression automatically.
Function<Integer, Integer> square = number -> number * number;
No explicit return statement is required.

Block Body

Multiple statements require braces.
Consumer<String> printer =
        name -> {
            System.out.println(name);
            System.out.println(name.length());
        };
A return statement becomes mandatory for non-void lambdas.
Function<Integer, Integer> cube =
        number -> {
            return number * number * number;
        };

Functional Interfaces

A lambda expression can only implement a functional interface, which contains exactly one abstract method.
@FunctionalInterface
interface Calculator {
    int calculate(int a, int b);
}
Implementation using a lambda:
Calculator calculator = (a, b) -> a + b;
The compiler automatically maps the lambda to the functional interface method.

@FunctionalInterface

The @FunctionalInterface annotation tells the compiler that an interface must contain exactly one abstract method.
@FunctionalInterface
interface Printer {
    void print(String message);
}
Compilation fails if additional abstract methods are added. The annotation is optional but recommended.

Built-in Functional Interfaces

The java.util.function package provides commonly used functional interfaces.
Predicate<T>
Function<T, R>
Consumer<T>
Supplier<T>
UnaryOperator<T>
BinaryOperator<T>
These interfaces eliminate the need to define custom functional interfaces for common operations.

Predicate

A Predicate accepts one argument and returns a boolean.
Predicate<Integer> even = number -> number % 2 == 0;
System.out.println(even.test(10));
Output:
true
Predicates are commonly used for filtering.

Function

A Function transforms one value into another.
Function<String, Integer> length = String::length;
System.out.println(length.apply("Java"));
Output:
4

Consumer

A Consumer accepts a value but returns nothing.
Consumer<String> printer = System.out::println;
printer.accept("Java");
Consumers typically perform side effects such as logging or printing.

Supplier

A Supplier returns a value without accepting any input.
Supplier<UUID> supplier = UUID::randomUUID;
System.out.println(supplier.get());

UnaryOperator

A UnaryOperator accepts and returns the same type.
UnaryOperator<String> upper = String::toUpperCase;

BinaryOperator

A BinaryOperator accepts two values of the same type and returns the same type.
BinaryOperator<Integer> multiply = (a, b) -> a * b;

Variable Capture

Lambda expressions can access variables from the enclosing scope.
String prefix = "Hello";

Consumer<String> printer =
        name -> System.out.println(prefix + name);
Captured local variables must be final or effectively final. The following is invalid.
int count = 0;

Consumer<String> printer =
        value -> System.out.println(count);

count++;
The compiler reports an error because count is modified.

this in Lambda Expressions

Inside a lambda, this refers to the enclosing class instance.
class Demo {
    void execute() {
        Runnable task = () -> System.out.println(this);
    }
}
This differs from anonymous classes, where this refers to the anonymous class instance.

Method References

A method reference is a shorthand for a lambda that simply invokes an existing method.
names.forEach(System.out::println);
Equivalent lambda:
names.forEach(
        name -> System.out.println(name)
);
Method references improve readability and reduce unnecessary code.

Types of Method References

Java supports four kinds of method references.

1. Static method reference.
Integer::parseInt
2. Instance method of a particular object.
printer::print
3. Instance method of an arbitrary object.
String::length
4. Constructor reference.
Employee::new
Constructor references create objects using existing constructors.
Supplier<Employee> supplier = Employee::new;
Employee employee = supplier.get();

Function Composition

The Function interface supports function composition using andThen() and compose().
Function<Integer, Integer> square = number -> number * number;
Function<Integer, Integer> addTen = number -> number + 10;
Function<Integer, Integer> result = square.andThen(addTen);

System.out.println(result.apply(5));
Output:
35

Final Notes

Lambda expressions introduced a concise and expressive way to represent behavior in Java.

They simplify the implementation of functional interfaces, reduce boilerplate, and enable a functional programming style that integrates seamlessly with streams, collections, and other modern Java APIs.
Nagesh Chauhan

Nagesh Chauhan

Principal Software Engineer • Java • Python • Distributed Systems • AI/ML

Principal Software Engineer with 14+ years of experience designing and delivering large-scale distributed systems, cloud-native applications, and AI-powered platforms.

Passionate about solving complex engineering problems using strong data structures and algorithms, along with expertise in Java, Spring Boot, Python, System Design, Microservices, Cloud, Kafka, Elasticsearch, and Generative AI.

Share this Article

💬 Comments

Join the Discussion