A Guide to Using Java Streams and Lambda Expressions for Selenium Automation

Samiksha KuteSamiksha Kute
10 min read

Java Streams and Lambda Expressions, introduced in Java 8, are powerful tools for writing optimized, concise, and efficient code. They simplify complex data processing tasks and are especially useful for test automation with tools like Selenium. In this blog post, we'll cover everything you need to know about Java Streams and Lambda Expressions, with a focus on Selenium WebDriver use cases.

Let’s get started!

Introduction to Java Streams and Lambda Expressions

What Are Java Streams?

Java Streams, introduced in Java 8, enable functional-style processing of collections (e.g., List, Set) or arrays. Streams allow you to perform aggregate operations such as filtering, mapping, or sorting on data with minimal code, reducing complexity and improving performance. Streams can process data sequentially (stream()) or in parallel (parallelStream()) for large datasets, making them ideal for automation tasks.

Why Use Streams?

  • Code Simplicity: Streams reduce the need for lengthy loops and conditional statements, often allowing you to achieve the same result in a single line.

  • Performance: Parallel streams (parallelStream()) leverage multi-core processors, though sequential streams are often sufficient for small datasets.

  • Maintainability: Streams produce clean, reusable code, essential for automation frameworks.

What Are Lambda Expressions?

Lambda expressions are a concise way to represent functions in Java. They consist of two parts:

  • Left Side: Parameters (e.g., s for a string).

  • Right Side: The action to perform on those parameters (e.g., s.startsWith("A")).

Example: s -> s.startsWith("A").

Lambdas integrate seamlessly with streams for filtering or transforming data. Java also supports method references (e.g., System.out::println) for even shorter syntax.

Note: This blog uses Java 21 (latest LTS as of May 2025) and Selenium 4.21 for modern features like toList() and Selenium Manager***.***

Getting Started with Streams: A Practical Example

Let’s dive into a hands-on example to understand streams and lambda expressions. Suppose you have a list of names, and you need to count how many names start with the letter "A". We’ll first solve this using traditional Java, then optimize it with streams.

Traditional Java Approach

Here’s how you’d write the code without streams:

import java.util.List;

public class Main {
    public static void main(String[] args) {
        // Create a list of names
        List<String> names = List.of("Alexandra", "Don", "Alok", "Adam", "Rama");

        // Initialize a counter
        int count = 0;

        // Loop through the list
        for (String name : names) {
            if (name.startsWith("A")) {
                count++;
            }
        }

        // Print the result
        System.out.println("Count: " + count); // Output: Count: 3
    }
}

While the above code works, it is lengthy and uses loops and manual counter.

Stream-Based Approach

Now, let’s rewrite the same logic using streams:

import java.util.List;

public class Main {
    public static void main(String[] args) {
        // Create a List of names
        List<String> names = List.of("Alexandra", "Don", "Alok", "Adam", "Rama");

        // Use streams to count names starting with "A"
        long count = names.stream()
                         .filter(s -> s.startsWith("A"))
                         .count();

        // Print the result
        System.out.println("Count: " + count); // Output: Count: 3
    }
}

This code:

  1. List.of() creates an immutable list.

  2. stream() converts the list to a stream.

  3. filter(s -> s.startsWith("A")) keeps names starting with "A".

  4. count() returns a long (used for large collections).

The stream version is concise and readable. For large datasets, use parallelStream() to leverage multi-core processing, but note that parallel streams add overhead for small datasets.

How Streams Work: The Three Stages

Streams operate in three stages:

  1. Create: Convert a collection to a stream (e.g., names.stream()).

  2. Intermediate Operations: Apply operations like filter, map, or sorted to transform the stream. These operations are lazy as they don’t execute until a terminal operation is called.

  3. Terminal Operation: Apply a final operation like count, forEach, or collect to produce a result. This triggers the execution of intermediate operations.

For example, in the code earlier:

names.stream()                     // Create
     .filter(s -> s.startsWith("A")) // Intermediate
     .count();                       // Terminal

Important Note: Intermediate operations (e.g., filter) only execute if there’s a terminal operation. Without a terminal operation, nothing happens.

Key Stream Operations Explained

Let’s explore common stream operations using practical examples.

1. Filter

Keeps elements matching a condition.

Example: Print names longer than 4 characters.

names.stream()
     .filter(s -> s.length() > 4)
     .forEach(System.out::println); // Method reference

Output:

Alexandra

2. Map

Transforms elements using a function.

Example: Print names ending with "a" in uppercase.

names.stream()
     .filter(s -> s.endsWith("a"))
     .map(String::toUpperCase) // Method reference
     .forEach(System.out::println);

Output:

ALEXANDRA
RAMA

3. Sorted

Sorts the elements in natural order (e.g., alphabetical for strings).

Example: Print names starting with "A" in sorted order, in uppercase.

// Modified list with an additional name
names.add("Abhijeet");

names.stream()
     .filter(s -> s.startsWith("A"))
     .sorted()
     .map(String::toUpperCase)
     .forEach(System.out::println);

Output:

ABHIJEET
ADAM
ALEXANDRA
ALOK

Note: sorted() uses full-string comparison, not just specific characters.

4. Limit

Restricts the number of elements.

Example: Print the first name longer than 4 characters.

names.stream()
     .filter(s -> s.length() > 4)
     .limit(1)
     .forEach(System.out::println);

Output:

Alexandra
  • limit(1) ensures only the first matching name is processed.

5. Distinct

Removes duplicates.

Example: Print unique numbers from an array.

int[] numbers = {3, 2, 2, 7, 5, 1, 9};
Arrays.stream(numbers)
      .distinct()
      .forEach(System.out::println);

Output:

3
2
7
5
1
9
  • distinct() ensures each number appears only once.

6. Concat

Merges two streams.

Example: Merge two lists and print the combined sorted list.

List<String> names1 = List.of("John", "Jane");
List<String> combined = Stream.concat(names.stream(), names1.stream())
                             .sorted()
                             .toList(); // Java 16+
combined.forEach(System.out::println);

Output:

Abhijeet
Adam
Alexandra
Alok
Don
Jane
John
Rama

Note: Streams are single-use. Collect to a list before further operations.

7. AnyMatch

Checks if any element matches a condition, returning a boolean.

Example: Check if "Adam" is in the merged list.

boolean flag = combined.stream()
                       .anyMatch(s -> s.equalsIgnoreCase("Adam"));
Assert.assertTrue(flag); // returns true

8. Collect

Converts a stream into a collection (e.g., List, Set).

Example: Collect names ending with "a" in uppercase into a list.

List<String> result = names.stream()
                           .filter(s -> s.endsWith("a"))
                           .map(String::toUpperCase)
                           .toList(); // Unmodifiable list (Java 16+)
System.out.println(result.get(0)); // Output: ALEXANDRA

9. TakeWhile (Java 9+)

Takes elements while a condition is true.

Example: Take names until one exceeds 5 characters.

names.stream()
     .takeWhile(s -> s.length() <= 5)
     .forEach(System.out::println);

Output:

Don
Alok
Adam
Rama

Streams in Selenium Automation

Streams are incredibly powerful when combined with Selenium for web automation. Let’s explore real-world scenarios using a web table on a practice page (a table listing vegetables and prices).

Scenario 1: Verify Web Table Sorting

Goal: Check if the vegetable names in a web table are sorted alphabetically after clicking the column header.

Algorithm:

  1. Click the column header to sort the table.

  2. Capture all vegetable names into a list (originalList).

  3. Create a sorted version of the list (sortedList).

  4. Compare originalList with sortedList. If equal, the table is sorted.

Code:

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.Assert;

public class TableSortTest {
    public static void main(String[] args) {
        WebDriver driver = new ChromeDriver();
        driver.get("https://rahulshettyacademy.com/seleniumPractise/#/offers");

        // Step 1: Click column header
        driver.findElement(By.xpath("//tr/th[1]")).click();

        // Step 2: Capture all web elements
        List<WebElement> elementsList = driver.findElements(By.xpath("//tr/td[1]"));

        // Step 3: Get text into originalList using streams
        List<String> originalList = elementsList.stream()
                                                .map(s -> s.getText())
                                                .toList();

        // Step 4: Create sortedList
        List<String> sortedList = originalList.stream()
                                              .sorted()
                                              .toList();

        // Step 5: Compare lists
        Assert.assertTrue(originalList.equals(sortedList), "Table is not sorted!");

        driver.quit();
    }
}

Explanation:

  • map(s -> s.getText()) extracts text from each WebElement.

  • sorted() creates a sorted version of the list.

  • Assert.assertTrue checks if the lists are equal.

  • Debugging: To test a negative case, pause execution after clicking the header, manually unsort the table, and resume. The assertion will fail if the lists differ.

Output: If the table is sorted, the test passes. If not, it fails with an error.

Scenario 2: Get the Price of a Specific Item

Goal: Find the price of a vegetable (e.g., "Beans") from the table.

Algorithm:

  1. Capture all vegetable names (first column).

  2. Filter for the desired vegetable (e.g., "Beans").

  3. Navigate to the sibling <td> element containing the price.

  4. Extract and print the price.

Code:

public class GetPriceTest {
    public static void main(String[] args) {
        WebDriver driver = new ChromeDriver();
        driver.get("https://rahulshettyacademy.com/seleniumPractise/#/offers");

        // Capture all web elements
        List<WebElement> rows = driver.findElements(By.xpath("//tr/td[1]"));

        // Filter for "Beans" and get price
        List<String> price = rows.stream()
                                 .filter(s -> s.getText().contains("Beans"))
                                 .map(s -> getPriceOfVeggie(s))
                                 .toList();

        // Print prices
        price.forEach(System.out::println);

        driver.quit();
    }

    private static String getPriceOfVeggie(WebElement s) {
        String priceValue = s.findElement(By.xpath("following-sibling::td[1]")).getText();
        return priceValue;
    }
}

Output:

38

Explanation:

  • filter(s -> s.getText().contains("Beans")) selects the WebElement for "Beans".

  • map(s -> getPriceOfVeggie(s)) calls a custom method to navigate to the price column (using XPath following-sibling::td[1]).

  • collect stores the price in a list.

  • forEach prints the price.

Scenario 3: Handle Pagination

Goal: Find the price of a vegetable (e.g., "Rice") that may not be on the first page.

Algorithm:

  1. Capture vegetable names on the current page.

  2. Filter for "Rice" and get its price.

  3. If not found (price.size() < 1), click the "Next" button and repeat.

  4. Use a do-while loop to iterate through pages until the vegetable is found or all pages are checked.

Code:

public class PaginationTest {
    public static void main(String[] args) {
        WebDriver driver = new ChromeDriver();
        driver.get("https://rahulshettyacademy.com/seleniumPractise/#/offers");

        List<String> price;
        do {
            // Capture all web elements
            List<WebElement> rows = driver.findElements(By.xpath("//tr/td[1]"));

            // Filter for "Rice" and get price
            price = rows.stream()
                        .filter(s -> s.getText().contains("Rice"))
                        .map(s -> getPriceOfVeggie(s))
                        .toList();

            // If not found, click next
            if (price.size() < 1) {
                driver.findElement(By.cssSelector("[aria-label='Next']")).click();
            }
        } while (price.size() < 1);

        // Print prices
        price.forEach(System.out::println);

        driver.quit();
    }

    private static String getPriceOfVeggie(WebElement s) {
        String priceValue = s.findElement(By.xpath("following-sibling::td[1]")).getText();
        return priceValue;
    }
}

Output:

37

Explanation:

  • The do-while loop ensures the script checks each page until price.size() >= 1.

  • If no match is found, the "Next" button is clicked using a CSS selector.

  • Stale Element Exception: The rows list is refreshed each iteration to avoid errors after page changes.

  • The script navigates to the fourth page (where "Rice" is located) and prints its price.

Scenario 4: Verify Filter Functionality

Goal: Validate that a search filter (e.g., typing "Rice" in a search box) displays only relevant results.

Algorithm:

  1. Enter "Rice" in the search box.

  2. Capture all vegetable names in the first column (veggies).

  3. Filter the list to ensure all elements contain "Rice" (filteredList).

  4. Compare the sizes of veggies and filteredList. If equal, the filter works correctly.

Code:

public class FilterTest {
    public static void main(String[] args) {
        WebDriver driver = new ChromeDriver();
        driver.get("URL_OF_WEB_TABLE");

        // Enter "Rice" in search box
        driver.findElement(By.id("search_field")).sendKeys("Rice");

        // Capture all web elements
        List<WebElement> veggies = driver.findElements(By.xpath("//tr/td[1]"));

        // Filter for elements containing "Rice"
        List<String> filteredList = veggies.stream()
                                          .filter(veggie -> veggie.getText().contains("Rice"))
                                          .map(veggie -> veggie.getText())
                                          .toList();

        // Compare list sizes
        Assert.assertEquals(veggies.size(), filteredList.size(), "Filter functionality failed!");

        driver.quit();
    }
}

Output: Test passes if the filter works (both lists have the same size). Fails otherwise.

Explanation:

  • sendKeys("Rice") triggers the filter.

  • filter ensures all displayed elements contain "Rice".

  • Assert.assertEquals checks if the filter correctly returned only matching results.

  • Debugging: To test a negative case, pause after entering "Rice", manually add non-matching items (e.g., "Wheat"), and resume. The assertion fails if the sizes differ.

Best Practices and Tips

  1. Use Streams for Optimization: Streams reduce code complexity and improve performance, making them ideal for automation frameworks.

  2. Leverage Lambda Expressions: They make stream operations concise and readable.

  3. Handle Pagination Smartly: Use do-while loops to navigate pages efficiently in Selenium.

  4. Validate XPath/CSS: Use tools like SelectorsHub to write and validate locators, but don’t rely solely on plugins.

  5. Debug Negative Cases: Test both positive and negative scenarios to ensure robust automation script

  6. Practice Regularly: Experiment with streams in Eclipse or IntelliJ, applying them to Selenium scenarios.

Staying Updated

Follow these resources:

Check for Java and Selenium updates, as features like WebDriver and Chrome DevTools integration are evolving.

Conclusion

Java Streams and Lambda Expressions simplify complex data processing, making your code concise, efficient, and maintainable. When combined with Selenium, they enable powerful automation scripts for web tables, pagination, and filters.

Check out the code repository below for sample scripts:

See you in the next blog!

For more such articles check out the complete Selenium Series.

Happy Coding! 🚀

0
Subscribe to my newsletter

Read articles from Samiksha Kute directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Samiksha Kute
Samiksha Kute

Passionate Learner!