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


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:
List.of()
creates an immutable list.stream()
converts the list to a stream.filter(s -> s.startsWith("A"))
keeps names starting with "A".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:
Create: Convert a collection to a stream (e.g.,
names.stream()
).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.
Terminal Operation: Apply a final operation like
count
,forEach
, orcollect
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:
Click the column header to sort the table.
Capture all vegetable names into a list (originalList).
Create a sorted version of the list (sortedList).
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:
Capture all vegetable names (first column).
Filter for the desired vegetable (e.g., "Beans").
Navigate to the sibling
<td>
element containing the price.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:
Capture vegetable names on the current page.
Filter for "Rice" and get its price.
If not found (
price.size() < 1
), click the "Next" button and repeat.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:
Enter "Rice" in the search box.
Capture all vegetable names in the first column (veggies).
Filter the list to ensure all elements contain "Rice" (filteredList).
Compare the sizes of
veggies
andfilteredList
. 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
Use Streams for Optimization: Streams reduce code complexity and improve performance, making them ideal for automation frameworks.
Leverage Lambda Expressions: They make stream operations concise and readable.
Handle Pagination Smartly: Use do-while loops to navigate pages efficiently in Selenium.
Validate XPath/CSS: Use tools like SelectorsHub to write and validate locators, but don’t rely solely on plugins.
Debug Negative Cases: Test both positive and negative scenarios to ensure robust automation script
Practice Regularly: Experiment with streams in Eclipse or IntelliJ, applying them to Selenium scenarios.
Staying Updated
Follow these resources:
Selenium Slack or Stack Overflow for community support.
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! 🚀
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!