In the previous lesson, the Excel reading code was inside the test class. That is fine for learning but terrible for a real project. If 10 test classes read Excel files, you do not want 10 copies of getCellValue(). You build one ExcelUtils class and every DataProvider across the project calls it.
package com.practice.utils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* Reusable Excel utility. Every DataProvider in the project
* calls this instead of writing its own POI code.
*/
public class ExcelUtils {
/**
* Reads all data rows from a named sheet.
* Row 0 is treated as header and skipped.
*
* @param filePath path to .xlsx file
* @param sheetName name of the sheet tab
* @return Object[][] ready for TestNG DataProvider
*/
public static Object[][] readSheet(String filePath,
String sheetName)
throws IOException {
List<Object[]> data = new ArrayList<>();
try (FileInputStream fis = new FileInputStream(filePath);
Workbook workbook = new XSSFWorkbook(fis)) {
Sheet sheet = workbook.getSheet(sheetName);
if (sheet == null) {
throw new RuntimeException(
"Sheet '" + sheetName
+ "' not found in " + filePath);
}
// Get column count from header row
Row headerRow = sheet.getRow(0);
if (headerRow == null) {
throw new RuntimeException(
"Header row is empty in sheet: "
+ sheetName);
}
int columnCount = headerRow.getLastCellNum();
// Read data rows (skip header at index 0)
for (int i = 1; i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
if (row == null) continue;
Object[] rowData = new Object[columnCount];
for (int j = 0; j < columnCount; j++) {
rowData[j] = getCellValue(row.getCell(j));
}
data.add(rowData);
}
}
return data.toArray(new Object[0][]);
}
/**
* Reads the first sheet when you do not care about
* the sheet name.
*/
public static Object[][] readFirstSheet(String filePath)
throws IOException {
try (FileInputStream fis = new FileInputStream(filePath);
Workbook workbook = new XSSFWorkbook(fis)) {
String firstSheetName =
workbook.getSheetAt(0).getSheetName();
return readSheet(filePath, firstSheetName);
}
}
/**
* Gets the number of data rows (excluding header).
*/
public static int getRowCount(String filePath,
String sheetName)
throws IOException {
try (FileInputStream fis = new FileInputStream(filePath);
Workbook workbook = new XSSFWorkbook(fis)) {
Sheet sheet = workbook.getSheet(sheetName);
if (sheet == null) return 0;
return sheet.getLastRowNum(); // header is row 0
}
}
/**
* Safely extracts a String value from any cell type.
* Handles null, STRING, NUMERIC, BOOLEAN, FORMULA,
* BLANK, and date-formatted cells.
*/
private static String getCellValue(Cell cell) {
if (cell == null) return "";
return switch (cell.getCellType()) {
case STRING -> cell.getStringCellValue().trim();
case NUMERIC -> {
if (DateUtil.isCellDateFormatted(cell)) {
yield cell.getDateCellValue().toString();
}
double num = cell.getNumericCellValue();
if (num == Math.floor(num)) {
yield String.valueOf((long) num);
}
yield String.valueOf(num);
}
case BOOLEAN -> String.valueOf(
cell.getBooleanCellValue());
case FORMULA -> {
try {
yield cell.getStringCellValue();
} catch (IllegalStateException e) {
yield String.valueOf(
cell.getNumericCellValue());
}
}
case BLANK -> "";
default -> "";
};
}
}import com.practice.utils.ExcelUtils;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.testng.Assert;
public class InsuranceQuoteTest extends BaseTest {
private static final String DATA_FILE =
"src/test/resources/test-data/insurance-data.xlsx";
@DataProvider(name = "quoteData")
public Object[][] quoteData() throws Exception {
return ExcelUtils.readSheet(DATA_FILE, "QuoteScenarios");
}
@Test(dataProvider = "quoteData")
public void testInsuranceQuote(String name, String age,
String coverage,
String expectedPremium) {
navigateTo("/insurance");
QuotePage quote = new QuotePage(driver);
quote.enterName(name);
quote.enterAge(age);
quote.selectCoverage(coverage);
quote.clickGetQuote();
Assert.assertEquals(
quote.getDisplayedPremium(), expectedPremium,
"Premium mismatch for: " + name
+ ", age=" + age
+ ", coverage=" + coverage);
}
}Q: How do you handle Excel reading in your framework? Do you write POI code in every test class?
A: No. We have a centralized ExcelUtils class in the utils package. It has a readSheet() method that takes a file path and sheet name, reads all rows (skipping the header), and returns Object[][] ready for TestNG DataProvider. The utility has a private getCellValue() method that handles all cell types — string, numeric, boolean, date, formula, and null. Every DataProvider in the project calls ExcelUtils instead of writing its own POI code.
Never forget to close the FileInputStream and Workbook. Use try-with-resources (like the code above) to auto-close them. If you skip this, the file stays locked on Windows and your next test run fails with "file is being used by another process."
Start with CSV files for simplicity. Only move to Excel if your team specifically needs it. CSV files are easier to version control with Git (you can see diffs), easier to edit in any text editor, and do not need Apache POI. Excel is heavier but wins when the business team manages the data.
Key Point: Build ExcelUtils once, use it everywhere. Never duplicate POI code across test classes.