Next-Gen App & Browser Testing Cloud
Trusted by 2 Mn+ QAs & Devs to accelerate their release cycles

DataProvider in TestNG is used to inject multiple values into the same test case, this guide explains how to use them in your Selenium test automation scripts.

Kritika Murari
May 11, 2026
DataProvider in TestNG is a @DataProvider-annotated method returning Object[][], so TestNG runs the linked @Test once per row. Per testng.org/parameters.html, @DataProvider also accepts Iterator<Object[]>, custom types like MyCustomData[][], a java.lang.reflect.Method first parameter, and parallel = true with a default 10-thread pool.
This article covers @DataProvider syntax, dataProviderClass inheritance, multi-value rows, Apache POI 5.x Excel rows, method-aware rows, parallel execution, four TestNG error strings, and a Selenium RemoteWebDriver cloud-grid snippet.
Overview
DataProviders in TestNG enable data-driven testing by passing multiple sets of input data to a single test method, reducing duplication and scaling test coverage in Selenium automation.
Why Do DataProviders Matter in TestNG?
TestNG @Parameters pass values once per execution. DataProviders run the same test once per data row, which is the building block for data-driven Selenium suites.
What Are the Core Pillars of TestNG DataProviders?
A DataProvider in TestNG is a method annotated with @DataProvider that returns a 2D array of objects. Each row is one test invocation. When a @Test method declares the matching DataProvider by name, TestNG calls the test once per row and passes the row values as arguments. According to the official TestNG documentation, DataProviders can also return Iterator<Object[]> for lazy evaluation when the dataset is large.
The base syntax is:
@DataProvider(name = "data-provider-name")
public Object[][] dataProviderFunc() {
return new Object[][] {
{"row-1-value-1", "row-1-value-2"},
{"row-2-value-1", "row-2-value-2"}
};
}For a refresher on how TestNG wires this together, see our TestNG annotations tutorial.
Note: Want to validate the same DataProvider rows across 10,000+ real browsers and devices? Spin up a free TestMu AI cloud Selenium Grid in minutes. Start free.
The video below walks through the TestMu AI TestNG certification, which covers DataProvider and other TestNG annotations end to end:
The minimal pattern: declare the DataProvider, link it from a @Test method with the same name. The example below runs the same TestMu Selenium Playground form twice, once per input row, using WebDriverManager so the test is not tied to a local chromedriver path.
package dataProviders;
import io.github.bonigarcia.wdm.WebDriverManager;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.Reporter;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
public class SimpleDataProviderTest {
WebDriver driver;
@DataProvider(name = "form-messages")
public Object[][] dataProvFunc() {
return new Object[][] {
{"TestMu AI cloud run"},
{"DataProvider iteration"}
};
}
@BeforeMethod
public void setUp() {
WebDriverManager.chromedriver().setup();
driver = new ChromeDriver();
driver.manage().window().maximize();
driver.get("https://www.testmuai.com/selenium-playground/simple-form-demo");
}
@Test(dataProvider = "form-messages")
public void enterMessage(String message) {
WebElement input = driver.findElement(By.id("user-message"));
input.clear();
input.sendKeys(message);
Reporter.log("Submitted: " + message);
}
@AfterMethod
public void tearDown() {
if (driver != null) driver.quit();
}
}TestNG runs enterMessage twice because the DataProvider returns two rows. The Reporter log captures each submitted value in the TestNG HTML report.


Two rows in, two test executions out, all from one method. The next section pulls the DataProvider into a dedicated class so test logic and test data stay separate.
If you also need event hooks around these runs, see TestNG Listeners in Selenium WebDriver.
Real test suites quickly outgrow one-file classes. Keep the DataProvider in its own class so several test classes can share the same dataset. Two changes are needed: mark the DataProvider method static, and reference its class from @Test via dataProviderClass.
DataProvider class:
package dataProviders;
import org.testng.annotations.DataProvider;
public class DPClass {
@DataProvider(name = "form-messages")
public static Object[][] dataProvFunc() {
return new Object[][] {
{"TestMu AI cloud run"},
{"DataProvider iteration"}
};
}
}Test class:
package dataProviders;
import io.github.bonigarcia.wdm.WebDriverManager;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.Reporter;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class TestClass {
WebDriver driver;
@BeforeMethod
public void setUp() {
WebDriverManager.chromedriver().setup();
driver = new ChromeDriver();
driver.manage().window().maximize();
driver.get("https://www.testmuai.com/selenium-playground/simple-form-demo");
}
@Test(dataProvider = "form-messages", dataProviderClass = DPClass.class)
public void enterMessage(String message) {
WebElement input = driver.findElement(By.id("user-message"));
input.clear();
input.sendKeys(message);
Reporter.log("Submitted: " + message);
}
@AfterMethod
public void tearDown() {
if (driver != null) driver.quit();
}
}The single new attribute, dataProviderClass = DPClass.class, lets any test class anywhere in the project reuse the same data rows. Output mirrors the previous run: one row per test invocation.

Each row in Object[][] can carry as many values as needed. Widen the inner array, then add matching parameters to the @Test method signature in the same order.
package dataProviders;
import io.github.bonigarcia.wdm.WebDriverManager;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.Reporter;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
public class MultiValueDataProviderTest {
WebDriver driver;
@DataProvider(name = "tool-and-language")
public Object[][] dataProvFunc() {
return new Object[][] {
{"Selenium", "Java"},
{"Playwright", "TypeScript"},
{"Appium", "Mobile"}
};
}
@BeforeMethod
public void setUp() {
WebDriverManager.chromedriver().setup();
driver = new ChromeDriver();
driver.manage().window().maximize();
driver.get("https://www.testmuai.com/selenium-playground/simple-form-demo");
}
@Test(dataProvider = "tool-and-language")
public void enterCombined(String tool, String language) {
WebElement input = driver.findElement(By.id("user-message"));
input.clear();
input.sendKeys(tool + " " + language);
Reporter.log("Submitted: " + tool + " " + language);
}
@AfterMethod
public void tearDown() {
if (driver != null) driver.quit();
}
}Three rows, three executions, two arguments per call. The output shows all three combined strings submitted to the playground form.

Hardcoded rows work for a handful of values. For real datasets, point the DataProvider at an external file. The next section uses Excel.
Reading test data from Excel keeps test rows under version-control-friendly files and lets non-developers update the dataset. The Apache POI library handles .xlsx parsing; the current stable line is Apache POI 5.x, which targets Java 8+.
Create a package such as testData under the project root and drop the spreadsheet (TestData.xlsx) inside it. Row 0 is the header, rows 1+ are test data:

Wire Apache POI into the DataProvider: a helper reads the sheet into a 2D String array, and the DataProvider returns it.
@DataProvider(name = "excel-data")
public Object[][] excelDP() throws IOException {
// Reads rows from a local xlsx file and returns them as a 2D array for TestNG.
return getExcelData(
"src/test/resources/testData/TestData.xlsx",
"Sheet1"
);
}
public String[][] getExcelData(String fileName, String sheetName) {
String[][] data = null;
try (FileInputStream fis = new FileInputStream(fileName);
XSSFWorkbook wb = new XSSFWorkbook(fis)) {
XSSFSheet sh = wb.getSheet(sheetName);
int noOfRows = sh.getPhysicalNumberOfRows();
int noOfCols = sh.getRow(0).getLastCellNum();
data = new String[noOfRows - 1][noOfCols];
for (int i = 1; i < noOfRows; i++) {
XSSFRow row = sh.getRow(i);
for (int j = 0; j < noOfCols; j++) {
Cell cell = row.getCell(j);
data[i - 1][j] = cell.getStringCellValue();
}
}
} catch (Exception e) {
System.out.println("Excel read failed: " + e.getMessage());
}
return data;
}Wire the DataProvider into a test class and the rest is identical to the multi-value pattern from the previous section.
package testNG;
import java.io.FileInputStream;
import java.io.IOException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import io.github.bonigarcia.wdm.WebDriverManager;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.Reporter;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
public class ExcelDataProvider {
WebDriver driver;
@BeforeMethod
public void setUp() {
WebDriverManager.chromedriver().setup();
driver = new ChromeDriver();
driver.manage().window().maximize();
driver.get("https://www.testmuai.com/selenium-playground/simple-form-demo");
}
@DataProvider(name = "excel-data")
public Object[][] excelDP() throws IOException {
return getExcelData(
"src/test/resources/testData/TestData.xlsx",
"Sheet1"
);
}
public String[][] getExcelData(String fileName, String sheetName) {
String[][] data = null;
try (FileInputStream fis = new FileInputStream(fileName);
XSSFWorkbook wb = new XSSFWorkbook(fis)) {
XSSFSheet sh = wb.getSheet(sheetName);
int noOfRows = sh.getPhysicalNumberOfRows();
int noOfCols = sh.getRow(0).getLastCellNum();
data = new String[noOfRows - 1][noOfCols];
for (int i = 1; i < noOfRows; i++) {
XSSFRow row = sh.getRow(i);
for (int j = 0; j < noOfCols; j++) {
Cell cell = row.getCell(j);
data[i - 1][j] = cell.getStringCellValue();
}
}
} catch (Exception e) {
System.out.println("Excel read failed: " + e.getMessage());
}
return data;
}
@Test(dataProvider = "excel-data")
public void enterCombined(String keyWord1, String keyWord2) {
WebElement input = driver.findElement(By.id("user-message"));
input.clear();
input.sendKeys(keyWord1 + " " + keyWord2);
Reporter.log("Submitted: " + keyWord1 + " " + keyWord2);
}
@AfterMethod
public void tearDown() {
if (driver != null) driver.quit();
}
}Running this prints one Reporter line per row in TestData.xlsx, the same way a hardcoded DataProvider would.

A single DataProvider can return different rows depending on which test method is calling it. Per the TestNG documentation: "If you declare your @DataProvider as taking a java.lang.reflect.Method as first parameter, TestNG will pass the current test method for this first parameter." Branch on method.getName() and one DataProvider feeds many tests.
import java.lang.reflect.Method;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
public class MethodAwareDataProvider {
@DataProvider(name = "by-method")
public Object[][] dpForMethod(Method method) {
if (method.getName().equals("loginTest")) {
return new Object[][] {
{"[email protected]", "Pass#1"},
{"[email protected]", "Pass#2"}
};
}
if (method.getName().equals("searchTest")) {
return new Object[][] {
{"TestMu AI", "Selenium"},
{"DataProvider", "TestNG"}
};
}
return new Object[][] {{"default-1", "default-2"}};
}
@Test(dataProvider = "by-method")
public void loginTest(String username, String password) {
// login flow uses login-specific rows
}
@Test(dataProvider = "by-method")
public void searchTest(String term1, String term2) {
// search flow uses different rows from the same DataProvider
}
}One DataProvider, two callers, two datasets. This avoids one DataProvider per test method when only the rows differ.
When the test row is I/O-bound (browser, network), DataProvider rows are easy to fan out across threads. Per the TestNG documentation, setting parallel = true on @DataProvider runs each row on its own thread, with a default pool of 10 threads. Override the pool size via data-provider-thread-count in the suite XML.
@DataProvider(name = "browser-matrix", parallel = true)
public Object[][] browserMatrix() {
return new Object[][] {
{"Chrome", "latest", "Windows 11"},
{"Firefox", "latest", "Windows 11"},
{"Edge", "latest", "macOS 14"},
{"Safari", "17", "macOS 14"}
};
}
@Test(dataProvider = "browser-matrix")
public void runOnBrowser(String browser, String version, String platform) {
// each row runs on its own thread
}And the matching suite XML if a larger pool is needed:
<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd">
<suite name="DataProviderSuite" data-provider-thread-count="8">
<test name="ParallelMatrix">
<classes>
<class name="com.testmu.tests.ParallelDataProviderTest" />
</classes>
</test>
</suite>Parallel DataProviders are most effective when each row drives a separate browser session. For full parallel suite control beyond a single DataProvider, see our guide on how to create a TestNG XML file and execute parallel testing.
Four errors account for almost every broken DataProvider in TestNG. The verbatim strings below come from TestNG's Parameters class on GitHub; once you can map a stack trace to one of them, each is mechanical to fix.
Local DataProvider runs are fine for tens of rows. Real cross-browser matrices need a cloud Selenium Grid. Swap the local ChromeDriver for a RemoteWebDriver pointing at the TestMu AI hub, and the same DataProvider rows execute across 10,000+ real browsers and devices. For full setup, see the TestMu AI Selenium automation getting-started docs.
import java.net.URL;
import java.util.HashMap;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
@BeforeMethod
public void setUp() throws Exception {
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("browserName", "Chrome");
caps.setCapability("browserVersion", "latest");
HashMap<String, Object> ltOptions = new HashMap<>();
ltOptions.put("platform", "Windows 11");
ltOptions.put("build", "TestNG DataProvider Demo");
ltOptions.put("name", "DataProvider Cloud Run");
ltOptions.put("username", System.getenv("LT_USERNAME"));
ltOptions.put("accessKey", System.getenv("LT_ACCESS_KEY"));
caps.setCapability("LT:Options", ltOptions);
driver = new RemoteWebDriver(
new URL("https://hub.lambdatest.com/wd/hub"),
caps
);
driver.get("https://www.testmuai.com/selenium-playground/simple-form-demo");
}Combine this with parallel = true on the DataProvider and an 8-thread suite to run the full browser matrix concurrently against TestMu AI online Selenium Grid. For mobile coverage, the same pattern works against the TestMu AI real device cloud.
Start by replacing your most-duplicated TestNG @Test methods with one method plus a @DataProvider, then move the rows into Excel as the dataset grows. Add parallel = true and a RemoteWebDriver pointing at the TestMu AI hub once you need cross-browser coverage at speed. For deeper TestNG plumbing, the Selenium automation testing with TestNG guide and the TestMu AI Selenium automation docs are the next two reads.
Did you find this page helpful?
More Related Hubs
TestMu AI forEnterprise
Get access to solutions built on Enterprise
grade security, privacy, & compliance