ZetCode

Java Lambda Expressions

Last modified October 13, 2026

Lambda expressions, introduced in Java 8, bring functional programming to Java. A lambda expression is an anonymous function: it has parameters and a body, but no name, and it provides an inline implementation of a functional interface. This tutorial covers the syntax, the most common functional interfaces, and practical use with arrays, collections, and streams.

The basic syntax of a lambda expression is as follows:

(parameters) -> expression
(parameters) -> { statements; }

The syntax can be abbreviated in several ways:

Sorting an Array

Sorting arrays is a common task in programming, and lambda expressions make it easier by providing a concise way to define comparison logic. The following example demonstrates sorting an array of strings alphabetically using a lambda expression.

com/zetcode/LambdaSortEx.java
package com.zetcode;

import java.util.Arrays;

public class LambdaSortEx {

    public static void main(String[] args) {

        String[] words = { "kind", "massive", "atom", "car", "blue" };

        Arrays.sort(words, (s1, s2) -> s1.compareTo(s2));

        System.out.println(Arrays.toString(words));
    }
}

This example demonstrates sorting an array of strings using the Arrays.sort method with a lambda expression. The lambda expression (s1, s2) -> s1.compareTo(s2) defines the comparison logic, resulting in an alphabetically sorted array.

$ java com/zetcode/LambdaSortEx.java
[atom, blue, car, kind, massive]

Functional Interfaces and Lambda Expressions

Functional interfaces, which contain a single abstract method, are the foundation for lambda expressions in Java. Lambda expressions provide a compact way to implement these interfaces. The optional @FunctionalInterface annotation asks the compiler to verify that an interface has exactly one abstract method. An interface may still contain default and static methods.

Implementing a Greeting Service

Creating custom functional interfaces allows developers to define specific behaviors. This example shows how to use a lambda expression to implement a simple greeting service that outputs messages to the console.

com/zetcode/LambdaGreetingEx.java
package com.zetcode;

@FunctionalInterface
interface GreetingService {
    void greet(String message);
}

public class LambdaGreetingEx {

    public static void main(String[] args) {

        GreetingService gs = msg -> System.out.println(msg);

        gs.greet("Good night");
        gs.greet("Hello there");
    }
}

In this example, a GreetingService functional interface is defined with a single greet method, marked with @FunctionalInterface. A lambda expression implements this interface, printing messages to the console when the greet method is invoked.

$ java com/zetcode/LambdaGreetingEx.java
Good night
Hello there

Common Functional Interfaces

The java.util.function package provides a set of built-in functional interfaces, such as Function, Consumer, Supplier, and Predicate. These interfaces support common functional programming patterns, making lambda expressions even more versatile.

Using the Function Interface

The Function interface is ideal for operations that take an input and produce an output. This example uses a lambda expression to define a function that computes the square of an integer.

com/zetcode/LambdaFunctionEx.java
package com.zetcode;

import java.util.function.Function;

public class LambdaFunctionEx {

    public static void main(String[] args) {

        Function<Integer, Integer> square = x -> x * x;
        System.out.println(square.apply(5));
    }
}

This example uses the Function interface to define a lambda expression that computes the square of an integer. The apply method executes the lambda, returning the result.

$ java com/zetcode/LambdaFunctionEx.java
25

Filtering Data with Streams

Java streams, combined with lambda expressions, provide a powerful way to process collections of data. This example demonstrates filtering a list of objects based on a condition defined by a lambda expression.

com/zetcode/LambdaFilterEx.java
package com.zetcode;

import java.util.List;

public class LambdaFilterEx {

    public static void main(String[] args) {

        List<User> persons = List.of(
                new User("Jack", "jack234@gmail.com"),
                new User("Peter", "pete2@post.com"),
                new User("Lucy", "lucy17@gmail.com"),
                new User("Robert", "bob56@post.com"),
                new User("Martin", "mato4@imail.com")
        );

        List<User> result = persons.stream()
                .filter(person -> person.email().endsWith("@post.com"))
                .toList();

        result.forEach(p -> System.out.println(p.name()));
    }
}

record User(String name, String email) {}

This example uses a lambda expression within a stream to filter User objects based on their email domain. The lambda expression in the filter method selects users whose email ends with "@post.com", the stream's toList method (Java 16+) collects the result, and another lambda in forEach prints the names.

$ java com/zetcode/LambdaFilterEx.java
Peter
Robert

Mapping Data with Streams

The map method in Java streams allows developers to transform data using lambda expressions. This example shows how to convert a list of strings into a list of their lengths.

com/zetcode/LambdaMapEx.java
package com.zetcode;

import java.util.List;

public class LambdaMapEx {

    public static void main(String[] args) {

        List<String> words = List.of("apple", "banana", "cherry");

        List<Integer> lengths = words.stream()
                .map(word -> word.length())
                .toList();

        System.out.println(lengths);
    }
}

This example demonstrates the use of a lambda expression with the map method to transform a list of strings into a list of their lengths. The lambda word -> word.length() computes the length of each word.

$ java com/zetcode/LambdaMapEx.java
[5, 6, 6]

Using Consumer Interface

The Consumer interface is used for operations that take an input and produce no output, such as printing or modifying data. This example uses a lambda expression to print strings in uppercase.

com/zetcode/LambdaConsumerEx.java
package com.zetcode;

import java.util.function.Consumer;

public class LambdaConsumerEx {

    public static void main(String[] args) {

        Consumer<String> printUpperCase = text -> System.out.println(text.toUpperCase());

        printUpperCase.accept("hello");
        printUpperCase.accept("java lambda");
    }
}

This example uses the Consumer interface to define a lambda expression that converts a string to uppercase and prints it. The accept method triggers the lambda's execution.

$ java com/zetcode/LambdaConsumerEx.java
HELLO
JAVA LAMBDA

Using Supplier Interface

The Supplier interface is used to generate values without taking any input. This example demonstrates a lambda expression that produces random greetings.

com/zetcode/LambdaSupplierEx.java
package com.zetcode;

import java.util.function.Supplier;

public class LambdaSupplierEx {

    public static void main(String[] args) {

        Supplier<String> randomGreeting = () -> {
            String[] greetings = {"Hello", "Hi", "Greetings"};
            return greetings[(int) (Math.random() * greetings.length)];
        };

        System.out.println(randomGreeting.get());
        System.out.println(randomGreeting.get());
    }
}

This example uses the Supplier interface to create a lambda expression that returns a random greeting. The get method retrieves the value produced by the lambda. Because the greetings are chosen randomly, the output differs between runs.

$ java com/zetcode/LambdaSupplierEx.java
Hi
Greetings

Combining Predicates

The Predicate interface is used for testing conditions. This example shows how to combine multiple predicates using logical operations to filter data.

com/zetcode/LambdaPredicateEx.java
package com.zetcode;

import java.util.List;
import java.util.function.Predicate;

public class LambdaPredicateEx {

    public static void main(String[] args) {

        List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8);

        Predicate<Integer> isEven = n -> n % 2 == 0;
        Predicate<Integer> isGreaterThanFour = n -> n > 4;

        List<Integer> result = numbers.stream()
                .filter(isEven.and(isGreaterThanFour))
                .toList();

        System.out.println(result);
    }
}

This example combines two Predicate lambda expressions to filter a list of numbers, selecting only even numbers greater than four. The and method combines the predicates logically.

$ java com/zetcode/LambdaPredicateEx.java
[6, 8]

Capturing Variables

A lambda expression can use local variables from its enclosing scope, but only if they are final or effectively final, meaning they are never reassigned after initialization. Unlike an anonymous class, a lambda does not introduce a new scope: this inside a lambda refers to the enclosing instance.

com/zetcode/LambdaCaptureEx.java
package com.zetcode;

import java.util.List;

public class LambdaCaptureEx {

    public static void main(String[] args) {

        String prefix = "Hello, ";
        List<String> names = List.of("Jack", "Lucy", "Peter");

        names.forEach(name -> System.out.println(prefix + name));
    }
}

The lambda captures the prefix variable. Assigning a new value to prefix anywhere in the method would cause a compile-time error.

$ java com/zetcode/LambdaCaptureEx.java
Hello, Jack
Hello, Lucy
Hello, Peter

Method References

When a lambda only calls an existing method, a method reference is a shorter alternative. The :: operator refers to static methods, instance methods, and constructors. In the example below, String::toUpperCase is equivalent to s -> s.toUpperCase() and System.out::println is equivalent to s -> System.out.println(s).

com/zetcode/MethodRefEx.java
package com.zetcode;

import java.util.List;

public class MethodRefEx {

    public static void main(String[] args) {

        List<String> words = List.of("kind", "massive", "atom", "car", "blue");

        words.stream()
                .map(String::toUpperCase)
                .sorted(String::compareTo)
                .forEach(System.out::println);
    }
}

The example converts the words to uppercase, sorts them, and prints each one.

$ java com/zetcode/MethodRefEx.java
ATOM
BLUE
CAR
KIND
MASSIVE

Summary

Lambda expressions provide concise implementations of functional interfaces. Combined with the built-in interfaces in java.util.function, method references, and streams, they make code for sorting, filtering, and mapping data shorter and easier to read. Prefer short lambdas; if a body grows beyond a few lines, move it into a named method and use a method reference.

Source

Java Lambda Expressions Documentation

Author

My name is Jan Bodnar, a dedicated programmer with extensive experience in software development. Since 2007, I have authored over 1,400 programming articles and eight e-books. With more than a decade of teaching experience, I am committed to sharing knowledge and fostering a deeper understanding of programming concepts.

List all Java tutorials.