Resource library

QA How-To

How to Use Selenium findElement and findElements in Java (2026)

Learn selenium findElement and findElements java syntax, return behavior, waits, scoped searches, list assertions, and reliable locator patterns for 2026.

22 min read | 3,149 words

TL;DR

In Java, `driver.findElement(By...)` returns the first matching `WebElement` and throws `NoSuchElementException` when no match appears within the applicable wait. `driver.findElements(By...)` returns every current match as a `List<WebElement>` and returns an empty list when there are none, which makes it the safer choice for optional elements and collection checks.

Key Takeaways

  • Use findElement when exactly one matching element is required and absence should fail the current step.
  • Use findElements when zero matches are a valid result or when the test must inspect a collection.
  • Expect findElement to throw NoSuchElementException and findElements to return an empty, never null, list when nothing matches.
  • Scope a search to a parent WebElement when repeated components contain similar descendants.
  • Use WebDriverWait and ExpectedConditions for dynamic UI state instead of Thread.sleep or large implicit waits.
  • Prefer stable IDs, compact CSS selectors, and explicit test attributes over positional or text-fragile XPath.
  • Assert collection size and meaning before indexing a List<WebElement>.

The exact selenium findElement and findElements java choice comes down to the contract of the test step. Use findElement(By...) when one element is required. Use findElements(By...) when you need a collection, when zero matches are meaningful, or when you want to check optional presence without catching an exception.

That simple rule prevents a surprising number of flaky checks. The methods also differ in return type, failure behavior, waiting behavior, and how safely they compose with page objects. This guide uses current Selenium 4 Java APIs, complete examples, and patterns suitable for maintainable UI automation in 2026.

TL;DR

Question findElement findElements
Return type WebElement List<WebElement>
If several elements match Returns the first match Returns all matches
If nothing matches Throws NoSuchElementException Returns an empty list
Best for Required unique control Repeated or optional controls
Safe presence check Usually no Yes, with isEmpty()
Typical assertion Element state or text Count, order, or aggregate state

A strong default is to make the locator unique, use findElement for a required control, and use findElements only when the list itself is part of the requirement. Add an explicit wait when the application changes asynchronously.

1. Selenium findElement and findElements Java at a Glance

Both methods belong to Selenium's SearchContext interface. WebDriver and WebElement implement that interface, so the same operation can search the whole current document or only the descendants of a known element. The Java signatures you use most often are conceptually:

WebElement findElement(By by);
List<WebElement> findElements(By by);

The By object describes how to locate a node. Selenium 4 Java code uses forms such as By.id("email"), By.cssSelector("[data-testid='product-card']"), and By.xpath("//button[normalize-space()='Save']"). Old convenience methods such as findElementById are not the current API.

findElement returns the first matching element in the active search context. First means the first result returned by the browser's locator evaluation, normally DOM order. It does not prove uniqueness. If two Save buttons match, the call succeeds and quietly selects one, which can make the test validate the wrong component. A uniqueness assertion or a more specific locator is appropriate when ambiguity would be a defect.

findElements returns a Java list containing all matches in the search context. An empty result is represented by an empty list, not null. That makes list operations such as isEmpty(), size(), iteration, and stream processing natural. The returned WebElement references can still become stale if the DOM replaces their nodes later.

2. Runnable Java Setup and First Example

Add org.seleniumhq.selenium:selenium-java from the current Selenium 4 release to a Maven or Gradle project. Recent Selenium releases include Selenium Manager, so a normal local setup can discover or obtain the matching browser driver when new ChromeDriver() creates a session. Your machine still needs a compatible browser and permission to download or execute the driver.

The following class is complete and uses an in-memory page, so it does not depend on a changing public website:

import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.List;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;

public class FindElementsDemo {
  public static void main(String[] args) {
    WebDriver driver = new ChromeDriver();
    try {
      String html = """
          <main>
            <h1>Inventory</h1>
            <ul id='products'>
              <li class='product' data-sku='A1'>Keyboard</li>
              <li class='product' data-sku='B2'>Mouse</li>
              <li class='product' data-sku='C3'>Monitor</li>
            </ul>
            <button id='checkout'>Checkout</button>
          </main>
          """;
      String url = "data:text/html;charset=utf-8;base64,"
          + Base64.getEncoder().encodeToString(html.getBytes(StandardCharsets.UTF_8));
      driver.get(url);

      WebElement checkout = driver.findElement(By.id("checkout"));
      List<WebElement> products = driver.findElements(By.cssSelector("#products .product"));

      System.out.println(checkout.getText());
      products.forEach(product -> System.out.println(product.getText()));

      if (products.size() != 3) {
        throw new AssertionError("Expected 3 products, got " + products.size());
      }
    } finally {
      driver.quit();
    }
  }
}

Use try and finally in examples and framework fixtures so quit() runs after an assertion or locator failure. Calling close() only closes the current window. Calling quit() ends the complete WebDriver session.

3. How findElement Behaves in Java

Use findElement when the page contract says the element must exist. A login form's username field, a checkout page's Place order button, and a result page's main heading are required controls. If the control is absent, NoSuchElementException is useful evidence that the expected UI state was not reached.

WebElement email = driver.findElement(By.name("email"));
email.sendKeys("qa@example.com");

WebElement submit = driver.findElement(By.cssSelector("button[type='submit']"));
submit.click();

The method returns a remote element reference, not a permanent snapshot of the HTML. Later calls such as click(), getText(), and getAttribute() operate through that reference. If a React, Angular, or Vue render replaces the original node, the old reference can throw StaleElementReferenceException. Locate the element again after the state transition instead of caching it indefinitely.

A less obvious problem is accidental non-uniqueness. If .submit matches a hidden mobile button and a visible desktop button, findElement can return the hidden one. Do not treat a successful return as proof that the selector is good. Check the locator in browser developer tools, scope it to the relevant form, or assert that a deliberate collection has one result:

List<WebElement> buttons = driver.findElements(
    By.cssSelector("form#payment button[type='submit']"));
if (buttons.size() != 1) {
  throw new AssertionError("Expected one payment submit button, got " + buttons.size());
}
buttons.get(0).click();

For more failure analysis, the Selenium NoSuchElementException guide explains how page state, frames, windows, and synchronization affect element lookup.

4. How findElements Behaves in Java

Use findElements when the requirement concerns many nodes or when absence is a normal state. Search results, menu items, table rows, validation messages, notification banners, and optional recommendations are collection problems. The method returns a List<WebElement> that you can inspect without exception handling for zero matches.

List<WebElement> errors = driver.findElements(By.cssSelector("[role='alert']"));
if (!errors.isEmpty()) {
  String combined = errors.stream()
      .map(WebElement::getText)
      .reduce((left, right) -> left + " | " + right)
      .orElse("");
  throw new AssertionError("Unexpected validation errors: " + combined);
}

The empty-list behavior is ideal for a quick presence query, but distinguish presence from visibility. A hidden element still exists in the DOM and appears in the result. !elements.isEmpty() answers "does at least one matching DOM node exist?" It does not answer "can a user see or interact with it?" Filter with WebElement::isDisplayed or, preferably, wait for an explicit visible-state condition when visibility matters.

Avoid indexing before asserting the size. driver.findElements(locator).get(0) replaces a meaningful Selenium exception with IndexOutOfBoundsException if the list is empty. It also suggests that findElement would express the required-one contract more clearly. When order matters, assert the expected size and content sequence before selecting a specific index.

The list is not live. It represents matches returned during that command. If the application adds another row, call findElements again. If it replaces existing rows, old references may be stale even though the Java list still contains them.

5. Choosing a Locator Strategy

The method and the locator solve different problems. findElement versus findElements decides cardinality. By.id versus CSS or XPath decides how Selenium identifies candidates. Good automation needs both decisions to be deliberate.

Strategy Java example Best use Frequent problem
ID By.id("order-number") Stable unique HTML ID Generated IDs change per run
Name By.name("email") Form fields with stable names Name is reused across forms
CSS By.cssSelector("[data-testid='cart-row']") Compact structural or attribute lookup Positional selectors couple to layout
XPath By.xpath("//section[@aria-label='Cart']//button[.='Remove']") Relationships or text unavailable to CSS Long absolute paths are brittle
Link text By.linkText("Account") Exact visible link labels Localization changes the label
Partial link text By.partialLinkText("Account") Stable fragment of a link Broad fragments match several links
Class name By.className("product-card") One literal class token It cannot accept card active as two classes
Tag name By.tagName("option") Descendants within a tight scope Broad page searches return noise

Prefer a unique, stable ID when the application owns one. A purpose-built attribute such as data-testid is often the best fallback for controls whose accessible text changes. CSS selectors are usually readable for attributes and component scope. XPath remains valuable for relationships, but a full path like /html/body/div[2]/div[1]/button breaks when layout changes.

A locator should describe identity, not styling. Utility classes, generated CSS module names, and nth-child encode implementation details. Review the Selenium CSS selector examples in Java and dynamic XPath techniques for focused patterns.

6. Scope Searches with SearchContext

Repeated components often contain the same labels and buttons. Searching the complete document for .price or a Remove button creates ambiguity. Find the component first, then search inside that WebElement. This uses the fact that both driver and element are SearchContext implementations.

List<WebElement> cards = driver.findElements(By.cssSelector("article.product-card"));

WebElement targetCard = cards.stream()
    .filter(card -> card.findElement(By.cssSelector("h2")).getText().equals("Monitor"))
    .findFirst()
    .orElseThrow(() -> new AssertionError("Monitor card was not found"));

String price = targetCard.findElement(By.cssSelector("[data-testid='price']")).getText();
targetCard.findElement(By.cssSelector("button.add-to-cart")).click();

A descendant search does not include the parent element itself. card.findElements(By.cssSelector("article.product-card")) searches beneath the card, not the card node. Also remember that a leading XPath // starts from the document in common browser evaluation behavior. Use .// when you intend a relative XPath beneath an element:

List<WebElement> badges = targetCard.findElements(By.xpath(".//*[@data-status='sale']"));

The same scoping principle applies to frames and shadow roots, but the context switch must be explicit. Switch into an iframe before searching its document. For an open shadow root, call element.getShadowRoot() and search the returned SearchContext. Do not lengthen a timeout when Selenium is simply searching the wrong context.

Page component objects are a natural home for scoped locators. A ProductCard can accept a root WebElement and expose name(), price(), and addToCart(), keeping page-level tests free of repeated descendant selectors.

7. Synchronize Dynamic Pages with Explicit Waits

Modern pages frequently render after the document load event. A correct locator can fail because the element has not appeared yet. Use WebDriverWait with an ExpectedCondition that describes the state needed by the next action. Do not add Thread.sleep(5000), which waits the full time and still makes no claim about readiness.

import java.time.Duration;
import java.util.List;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

WebElement checkout = wait.until(
    ExpectedConditions.elementToBeClickable(By.id("checkout")));
checkout.click();

List<WebElement> rows = wait.until(
    ExpectedConditions.numberOfElementsToBeMoreThan(
        By.cssSelector("#orders tbody tr"), 0));

presenceOfElementLocated requires a DOM node. visibilityOfElementLocated also requires it to be displayed with positive dimensions. elementToBeClickable checks visible and enabled state, although a later overlay can still intercept a click. For a list, use presenceOfAllElementsLocatedBy, visibilityOfAllElementsLocatedBy, or a count condition that matches the requirement.

Avoid mixing a large implicit wait with explicit waits. An implicit wait applies to element lookup calls and can make explicit-wait polling duration hard to reason about. A common framework choice is a zero implicit wait plus explicit waits around meaningful UI transitions. If your team uses a small implicit wait, measure its interaction with nested lookup conditions.

A wait should locate inside its polling function if the DOM can replace the element. Waiting on a cached reference cannot repair staleness. The fluent-wait customization pattern is covered in the Selenium fluent wait Java guide.

8. Work Safely with List

Collection assertions should express business meaning, not merely exercise Java loops. Start by asserting cardinality. Then transform the list into stable values such as text, IDs, or prices while the page is in a known state. Comparing value objects produces clearer failures than comparing remote element references.

List<String> actualNames = driver
    .findElements(By.cssSelector("[data-testid='product-name']"))
    .stream()
    .filter(WebElement::isDisplayed)
    .map(WebElement::getText)
    .map(String::trim)
    .toList();

List<String> expectedNames = List.of("Keyboard", "Monitor", "Mouse");
if (!actualNames.equals(expectedNames)) {
  throw new AssertionError("Expected " + expectedNames + " but got " + actualNames);
}

Stream.toList() is available in modern Java. If the project targets an older runtime, collect with Collectors.toList(). The more important issue is timing: a sequence of remote getText() calls can observe different UI states if live updates continue. Wait until loading finishes, then capture the values.

For uniqueness, collect a stable attribute and compare set size to list size. For sorting, parse values rather than comparing formatted currency strings. For row-level failures, include a key such as order ID in the assertion message. Avoid a single generic assertion like "table invalid".

When only the count matters, avoid extracting every element's text. findElements(locator).size() is adequate after the correct wait. When a specific row matters, prefer locating by its stable key within the table rather than retrieving every row and choosing a positional index.

9. Test Tables, Menus, and Repeated Components

A robust table test scopes in layers: table, row, then cell. This makes duplicate labels elsewhere irrelevant and gives the code a natural place to validate row identity. The following helper finds a row by an order ID and returns an empty Optional when the order is legitimately optional:

import java.util.Optional;

static Optional<WebElement> findOrderRow(WebDriver driver, String orderId) {
  WebElement table = driver.findElement(By.cssSelector("table[data-testid='orders']"));
  return table.findElements(By.cssSelector("tbody tr")).stream()
      .filter(row -> row.findElement(By.cssSelector("[data-column='id']"))
          .getText().equals(orderId))
      .findFirst();
}

If the row is required, end with orElseThrow and an informative assertion. If it appears after an API response, put the lookup inside wait.until so every poll retrieves the current rows. Do not hold a list from before the refresh.

Menus have a similar issue. A page can contain hidden desktop and mobile navigation at the same time. A collection followed by filter(WebElement::isDisplayed) can identify the active variant, but the test should still assert that exactly one item remains. Repeated component markup also favors stable data attributes and scoped CSS over global link text.

Virtualized lists need a different strategy because only visible rows exist in the DOM. findElements cannot return items that have not been rendered. Test the data through the application API when appropriate, then separately test scrolling and rendering behavior. Treat the current DOM count as a rendering observation, not the total dataset size.

10. Use finders Cleanly in Page Objects

Page objects should expose user or business operations, not leak every WebElement to tests. Keep By locators as fields when an element can be re-rendered. Locate close to the interaction so the reference is current. Return values or component objects when callers need information.

import java.time.Duration;
import java.util.List;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

public final class InventoryPage {
  private final WebDriver driver;
  private final WebDriverWait wait;
  private final By cards = By.cssSelector("article[data-testid='product-card']");
  private final By checkout = By.cssSelector("[data-testid='checkout']");

  public InventoryPage(WebDriver driver) {
    this.driver = driver;
    this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
  }

  public List<String> productNames() {
    return wait.until(ExpectedConditions.presenceOfAllElementsLocatedBy(cards))
        .stream()
        .map(card -> card.findElement(By.cssSelector("h2")).getText())
        .toList();
  }

  public void checkout() {
    wait.until(ExpectedConditions.elementToBeClickable(checkout)).click();
  }
}

Avoid a generic wrapper that catches every NoSuchElementException and returns null. It erases the required-versus-optional distinction. If optional presence is part of the domain, name it clearly and return boolean, Optional<Component>, or a list. If a required element is missing, preserve the Selenium stack and add page-specific context.

PageFactory can still locate elements, but direct By fields and explicit methods make timing and lookup behavior visible. Choose the pattern the team can debug under CI pressure. The important design rule is that element cardinality remains explicit.

11. Diagnose NoSuchElement and Stale Element Failures

When findElement throws NoSuchElementException, first verify the state, not just the selector. Record the current URL, page title, a screenshot, and a small relevant DOM fragment. Confirm that login succeeded, the correct tab is active, and any iframe switch occurred. Then evaluate the locator in developer tools against the same state.

A StaleElementReferenceException means Selenium found a node earlier, but that node is no longer attached to the current DOM. Lists are especially vulnerable when a filter, sort, or polling update replaces rows. Re-locate after the transition:

By status = By.cssSelector("[data-testid='job-status']");
wait.until(ExpectedConditions.textToBe(status, "Complete"));
String finalStatus = driver.findElement(status).getText();

Do not catch StaleElementReferenceException in an unbounded loop. A retry needs a time limit and a reason to expect the page to stabilize. Selenium's ExpectedConditions.refreshed(...) can help with a condition affected by redraws, but a locator-based condition is usually easier to understand.

If findElements unexpectedly returns empty, remember that frames, windows, shadow roots, and virtualized content define separate search boundaries. Also check whether the selector contains an escaped character or matches a generated attribute. Increasing the wait cannot fix the wrong browsing context.

Good diagnostics include the locator string and expected state. Avoid logging the entire page source by default because it is noisy and may contain sensitive data. Capture focused, redacted evidence on failure.

12. Selenium findElement and findElements Java Decision Guide

Ask four questions before choosing a method. First, how many elements does the product contract allow? Second, is zero a valid result? Third, what user-visible state must be true before lookup? Fourth, can the DOM replace the elements after lookup?

Use this practical mapping:

  1. Exactly one required control: findElement, normally through an explicit visibility or clickability wait.
  2. Zero or one optional banner: findElements plus a size assertion of at most one.
  3. Known number of repeated rows: wait for the expected count, then assert row values.
  4. Unknown number of search results: findElements, then assert domain rules such as nonempty, sorted, or filtered.
  5. Dynamic row replaced after refresh: store a By, wait using that locator, and retrieve a fresh element.
  6. Similar child controls in repeated cards: find all card roots, select by stable identity, then search within the chosen card.
  7. Element inside an iframe or open shadow root: switch or obtain the correct search context before either method.

Do not select findElements only to avoid an exception. If the element is required, an empty list followed by a vague assertion is less direct than a well-timed findElement. Conversely, do not use exception handling as ordinary control flow for optional UI. The method should document the expected cardinality before the first assertion runs.

Interview Questions and Answers

Q: What is the main difference between findElement and findElements in Selenium Java?

findElement returns the first matching WebElement and throws NoSuchElementException if nothing matches within the applicable wait. findElements returns all matches in a List<WebElement> and returns an empty list when there are none. I choose based on whether absence is a test failure or a valid collection state.

Q: Does findElement prove that a locator is unique?

No. If several nodes match, it returns the first match. For a control that must be unique, I improve the locator or assert that findElements(locator).size() equals one before interacting.

Q: How do implicit waits affect these methods?

The implicit timeout applies to element searches. A single-element lookup polls until it finds a match or times out, while a multiple-element lookup can wait before returning an empty list when no match exists. I avoid large implicit waits and use explicit waits for the exact dynamic state.

Q: Why can a List<WebElement> become unsafe after it is returned?

The Java list remains, but each item is a remote reference to a particular DOM node. If the application replaces those nodes, operations on them can throw StaleElementReferenceException. I re-run the locator after the transition and extract stable values only after the page settles.

Q: What is a scoped element search?

It is a lookup performed from a parent WebElement or another SearchContext instead of from the driver. It limits matching to descendants, which is useful for cards, tables, dialogs, open shadow roots, and other repeated components.

Q: Would you use findElements to check whether a modal is visible?

Not by itself. A nonempty list proves DOM presence, not visibility. I use an explicit visibility or invisibility condition, and I use findElements only if optional presence or cardinality is also part of the requirement.

Q: How do you choose between CSS and XPath?

I prefer stable IDs or test attributes, usually expressed with ID or CSS. I use XPath when a relationship or normalized text cannot be expressed clearly with CSS. In both cases I avoid positional selectors and long paths tied to layout.

Common Mistakes

  • Calling findElements(locator).get(0) without checking the list, which creates an IndexOutOfBoundsException and obscures the intended contract.
  • Assuming findElement validates uniqueness, even though it returns the first of several matches.
  • Using findElements presence as a visibility or clickability check.
  • Caching a WebElement or list across a React or Angular re-render.
  • Searching the whole document for a child that should be scoped to a card, row, form, or dialog.
  • Using // instead of .// for an intended relative XPath from an element.
  • Searching before switching to the correct iframe, window, or shadow root.
  • Combining long implicit waits, explicit waits, and repeated nested searches without understanding the total timeout.
  • Adding Thread.sleep around a weak locator instead of waiting for a meaningful state.
  • Building selectors from generated classes, DOM indexes, or exact text that changes with localization.
  • Catching NoSuchElementException and returning null for every page-object method.
  • Forgetting driver.quit() after a failure, leaving browser processes and Grid sessions behind.

A useful code-review question is: does the selected finder express the expected number of elements, and does the wait express the state the next action needs? If either answer is unclear, the test is likely to be difficult to diagnose.

Conclusion

For selenium findElement and findElements java code, use findElement for one required element and findElements for collections or valid absence. Make uniqueness, visibility, timing, and search context explicit rather than relying on whichever call happens not to throw.

Start by reviewing one unstable page object. Replace cached elements with locators, scope repeated components, add a state-specific WebDriverWait, and assert list cardinality before indexing. Those small changes produce failures that explain the product state instead of hiding it.

Interview Questions and Answers

Explain findElement versus findElements in Selenium Java.

`findElement` returns the first match and throws `NoSuchElementException` if no match appears. `findElements` returns every match as a list and returns an empty list for no matches. I use the first for a required single control and the second for collections or legitimate absence.

How would you verify that a locator matches exactly one element?

I call `findElements` with the locator and assert that the list size is one, then interact with that only element. This is useful when duplicate controls would indicate an accessibility or page-structure defect. For routine interactions, I first try to make the locator intrinsically unique.

How does SearchContext improve locator design?

`WebDriver`, `WebElement`, and open shadow roots can serve as search contexts. Scoping a child lookup to its component root removes unrelated matches and makes selectors shorter. It also lets page component objects own their descendant locators.

Why is an empty list preferable to catching NoSuchElementException for optional UI?

An empty list is the documented result of a collection query and represents valid absence directly. Exception handling should not be ordinary branching for a normal product state. The list also makes a zero-or-one cardinality assertion straightforward.

How do you prevent stale element failures with dynamic lists?

I wait for the transition using a locator-based condition and call `findElements` again after the DOM update. I do not cache row elements across sorting, filtering, or refresh operations. When possible, I extract stable values after the list is settled.

What locator strategy do you prefer in Selenium Java?

I prefer stable unique IDs, accessible identity, or explicit test attributes, usually through ID or CSS selectors. XPath is appropriate for relationships or normalized text when CSS is not expressive enough. I avoid generated classes, positions, and absolute paths.

What is wrong with findElements(locator).get(0)?

It hides whether zero, one, or many matches are acceptable and throws `IndexOutOfBoundsException` when empty. If one element is required, `findElement` expresses that better. If a collection is required, I assert its size before indexing.

Frequently Asked Questions

What does findElement return in Selenium Java?

It returns the first `WebElement` matching the supplied `By` locator in the current search context. If no match is found within the applicable wait, it throws `NoSuchElementException`.

What does findElements return when no element is found?

It returns an empty `List<WebElement>`, not `null`. This makes `isEmpty()` and `size()` appropriate for optional elements and collection assertions.

Can findElement return more than one element?

No, its return type is one `WebElement`. If several nodes match, Selenium returns the first, so the caller must use a more specific locator or a list-size assertion when uniqueness matters.

How do I wait for multiple elements in Selenium Java?

Use `WebDriverWait` with a condition such as `presenceOfAllElementsLocatedBy`, `visibilityOfAllElementsLocatedBy`, or `numberOfElementsToBe`. Choose the condition that matches the user-visible requirement.

Is findElements a good way to check visibility?

No, it reports matching DOM nodes, including hidden nodes. Use a visibility-focused expected condition or inspect `isDisplayed()` after asserting the collection's intended cardinality.

Can I call findElement on a WebElement?

Yes. `WebElement` implements `SearchContext`, so `parent.findElement(childLocator)` searches the parent's descendants. This is useful for repeated cards, rows, and dialogs.

Why does a previously found element become stale?

The application removed or replaced the exact DOM node represented by the WebElement reference. Store the locator and find the element again after the state transition instead of reusing the old reference.

Related Guides