Katalon keeps throwing NullPointerException when reading Excel rows

Hey everyone, I’m really struggling with a NullPointerException in my Katalon Studio test suite and I can’t seem to figure out how to bypass it.

I am trying to data-drive my test cases using an Excel spreadsheet. Everything works perfectly when all the rows are completely filled out. However, my Excel sheet has some blank rows at the bottom (and occasionally a few empty rows in the middle where data was deleted).

Every time my Groovy script hits one of these empty rows, the execution completely crashes with this error: java.lang.NullPointerException: Cannot invoke method... on a null object

Here is what I’ve tried so far:

  1. I tried using a basic if (row == null) check, but it still seems to fail or bypass the wrong steps.

  2. I tried manually deleting the rows in Excel, but Excel seems to keep the “ghost” formatting, so Katalon still thinks a row exists and tries to read it.

  3. I tried putting a try-catch block around the whole data reading loop, but that just stops my entire test execution instead of moving on to the next valid row.

I just want my script to safely check if a row or a cell is empty, skip it, and move on to the next row without crashing the whole test. How do I handle this properly in Groovy?

The NullPointerException (NPE) occurs because of how Apache POI (the underlying library Katalon uses to read Excel files) handles empty rows and cells.

When a row has had its content cleared but retains formatting, or when Katalon reads past the utilized data zone, Apache POI returns a null object for that row or cell. Attempting to call methods (like .toString() or getCell()) on a null object immediately halts execution.

To build a scalable, production-grade automation framework, you must implement a validation layer that checks for both null row objects and blank cell values before extracting data.

The Solution

The most efficient, industry-standard way to handle this in Katalon Studio is to create a Custom Keyword. This encapsulates the safety logic, keeps your test cases clean, and makes the Excel-reading utility reusable across your entire team.

We will create a helper method that checks if a row is completely null or if all its core cells are empty. If it is empty, the script will safely skip it.

Step-by-Step Implementation

  1. Go to the Keywords folder in Katalon Studio.

  2. Right-click and create a New Keyword. Name the package com.helpers and the class ExcelUtils.

  3. Paste the following code into the Custom Keyword:

Groovy

package com.helpers

import com.kms.katalon.core.annotation.Keyword
import org.apache.poi.ss.usermodel.Row
import org.apache.poi.ss.usermodel.Cell
import org.apache.poi.ss.usermodel.CellType

public class ExcelUtils {

    /**
     * Checks if an Excel row is null or entirely empty.
     * @param row The Apache POI Row object to validate.
     * @return boolean True if the row is null or contains no data, False otherwise.
     */
    @Keyword
    public static boolean isRowEmpty(Row row) {
        if (row == null) {
            return true
        }
        
        // Loop through all cells in the row to see if any contain data
        for (int c = row.getFirstCellNum(); c < row.getLastCellNum(); c++) {
            Cell cell = row.getCell(c)
            if (cell != null && cell.getCellType() != CellType.BLANK && !cell.toString().trim().isEmpty()) {
                return false // Found actual data, row is NOT empty
            }
        }
        return true // All cells are empty or null
    }
}

How to use it in your Test Script

Now, in your actual Katalon Test Case (Script view), you can use this keyword to safely skip bad data rows inside your loop:

Groovy

import internal.GlobalVariable as GlobalVariable
import org.apache.poi.ss.usermodel.Workbook
import org.apache.poi.ss.usermodel.Sheet
import org.apache.poi.ss.usermodel.Row
// Import your custom keyword
import com.helpers.ExcelUtils

// ... (Your code to load the Workbook and Sheet) ...
Workbook workbook = CustomKeywords.'com.helpers.ExcelExcelSupport.getWorkbook'(excelPath)
Sheet sheet = workbook.getSheetAt(0)

for (int i = 0; i <= sheet.getLastRowNum(); i++) {
    Row row = sheet.getRow(i)
    
    // Safely check and skip empty rows using the Custom Keyword
    if (CustomKeywords.'com.helpers.ExcelUtils.isRowEmpty'(row)) {
        println("Row " + i + " is empty. Skipping...")
        continue // Jumps to the next iteration of the loop
    }
    
    // Your actual test logic goes here safely:
    String username = row.getCell(0).toString()
    println("Processing user: " + username)
}

Architectural Benefits

  • Null-Safety: Pre-evaluates the row reference before calling any Apache POI methods, completely eliminating NullPointerException.

  • Code Reusability: The isRowEmpty keyword can be called across hundreds of test cases without duplicating code.

  • Maintains Test Continuity: Instead of failing the entire suite, invalid data is gracefully logged and skipped.

The NullPointerException occurs because Apache POI (the library Katalon uses for Excel) returns null for empty rows or cells. When you try to call methods like .toString() or .getCell() on a null object, execution crashes immediately.

Root Cause: Apache POI Returns null for Empty Rows

Situation What Apache POI Returns
Row with content cleared but formatting retained null row object
Cell that’s blank or never populated null cell object
Reading past utilized data zone null row object

Calling methods on nullNullPointerException crashes your test.

The Solution: Create a Custom Keyword for Null-Safety

Create a reusable helper that checks if a row is null or entirely empty before extracting data.

Step 1: Create the Custom Keyword

  1. Go to Keywords folder in Katalon Studio

  2. Right-click → New Keyword

  3. Package: com.helpers | Class: ExcelUtils

  4. Paste this code:

**`package com.helpers`

`import com.kms.katalon.core.annotation.Keyword`
`import org.apache.poi.ss.usermodel.Row`
`import org.apache.poi.ss.usermodel.Cell`
`import org.apache.poi.ss.usermodel.CellType`

`public class ExcelUtils {`
**
    ***`/**`
`     * Checks if an Excel row is null or entirely empty.`
`     * @param row The Apache POI Row object to validate.`
`     * @return boolean True if the row is null or contains no data, False otherwise.`
`     */`*****
`    @Keyword`
`    public static boolean isRowEmpty(Row row) {`
`        if (row == null) {`
`            return true`
`        }`**
        
        ***`// Loop through all cells to see if any contain data`*****
`        for (int c = row.getFirstCellNum(); c < row.getLastCellNum(); c++) {`
`            Cell cell = row.getCell(c)`
`            if (cell != null && cell.getCellType() != CellType.BLANK && !cell.toString().trim().isEmpty()) {`
`                return false `*`// Found actual data, row is NOT empty`*
`            }`
`        }`
`        return true `*`// All cells are empty or null`*
`    }`
`}`**

Step 2: Use It in Your Test Script

**`import internal.GlobalVariable as GlobalVariable`
`import org.apache.poi.ss.usermodel.Workbook`
`import org.apache.poi.ss.usermodel.Sheet`
`import org.apache.poi.ss.usermodel.Row`
`import com.helpers.ExcelUtils`

*`// Load workbook and sheet`*
`Workbook workbook = CustomKeywords.'com.helpers.ExcelExcelSupport.getWorkbook'(excelPath)`
`Sheet sheet = workbook.getSheetAt(0)`

`for (int i = 0; i <= sheet.getLastRowNum(); i++) {`
`    Row row = sheet.getRow(i)`**
    
    ***`// Safely skip empty rows using the Custom Keyword`*****
`    if (CustomKeywords.'com.helpers.ExcelUtils.isRowEmpty'(row)) {`
`        println("Row " + i + " is empty. Skipping...")`
`        continue `*`// Jump to next iteration`*
`    }`**
    
    ***`// Your actual test logic (safe now - row is verified non-empty)`*****
`    String username = row.getCell(0).toString()`
`    println("Processing user: " + username)`
`}`**

Why This Works

Benefit How It Helps
Null-Safety Pre-evaluates row reference before calling Apache POI methods → eliminates NPE
Code Reusability isRowEmpty() can be called across hundreds of test cases without duplicating code
Test Continuity Invalid data is gracefully logged and skipped instead of failing the entire suite

Why Your Previous Attempts Failed

Approach Problem
if (row == null) Only checks for null row, but doesn’t check if cells are BLANK type
Deleting rows in Excel Excel keeps “ghost” formatting → Katalon still sees the row
try-catch around loop Stops entire test execution instead of moving to next valid row

Bottom line: Use isRowEmpty() custom keyword to check both null rows and blank cells before extracting data

This issue is very common when working with Excel files in Katalon, Apache POI, or custom Groovy scripts.

The root cause is that a row can physically exist in Excel but still contain no data. Similarly, a cell may be null even though the row itself exists. This is why a simple if(row == null) check often doesn’t solve the problem.

Why the NullPointerException occurs

Consider code like this:

String username = row.getCell(0).getStringCellValue()

If either:

  • row is null, or
  • row.getCell(0) is null,

Katalon/Groovy throws a NullPointerException.

Many users only check the row:

if (row != null) {
    String username = row.getCell(0).getStringCellValue()
}

But if the cell itself is empty, the script still fails.


Recommended Solution: Check Both Row and Cell

Before reading any value, verify that the row and required cells exist.

if (row == null) {
    println("Skipping empty row")
    continue
}

def cell = row.getCell(0)

if (cell == null) {
    println("Skipping row with empty cell")
    continue
}

String value = cell.toString().trim()

if (value.isEmpty()) {
    println("Skipping blank row")
    continue
}

Using continue is important because it skips only the current iteration and moves to the next row instead of terminating the entire execution.


Better Approach: Detect Completely Empty Rows

Sometimes a row exists because Excel retained formatting, but all cells are blank.

Create a reusable helper method:

boolean isRowEmpty(row) {
    if (row == null) {
        return true
    }

    for (int cellNum = row.getFirstCellNum(); cellNum < row.getLastCellNum(); cellNum++) {
        def cell = row.getCell(cellNum)

        if (cell != null && cell.toString().trim()) {
            return false
        }
    }

    return true
}

Usage:

if (isRowEmpty(row)) {
    println("Skipping empty row")
    continue
}

This handles:

  • Completely blank rows
  • Deleted rows
  • Excel “ghost” rows with formatting only
  • Rows where all cells are empty

If Using Apache POI Directly

A safer pattern is:

for (int i = sheet.getFirstRowNum(); i <= sheet.getLastRowNum(); i++) {

    def row = sheet.getRow(i)

    if (row == null || isRowEmpty(row)) {
        continue
    }

    // Process valid row
}

This prevents the script from attempting to read non-existent data.


About try-catch

A try-catch block should be used for unexpected exceptions, not as the primary mechanism for handling empty rows.

Instead of:

try {
    // Read row
} catch(Exception e) {
    break
}

use proper validation first:

if (row == null || isRowEmpty(row)) {
    continue
}

This keeps the execution clean and avoids masking real issues.

Best Practice

When reading Excel data in Katalon:

  1. Always check whether the row is null.
  2. Always check whether the cell is null before accessing its value.
  3. Use a reusable isRowEmpty() method to handle blank or formatted-only rows.
  4. Use continue to skip invalid rows instead of stopping execution.
  5. Reserve try-catch for genuine exceptions, not for normal empty-row handling.

This approach will allow your test to safely ignore blank rows anywhere in the spreadsheet and continue processing the remaining valid test data without crashing.

the problem is Apache POI returns null for empty rows and for cells that were never populated, so calling .getCell() or .toString() on a null object crashes immediately. A simple if (row == null) only catches half the issue because a row can exist with all null cells

the cleanest fix is a helper method that checks both conditions, then use continue to skip bad rows instead of try-catch (which exits the whole loop if you’re using break instead of continue)

def isRowEmpty(Row row) {
    if (row == null) return true
    for (int c = row.getFirstCellNum(); c < row.getLastCellNum(); c++) {
        def cell = row.getCell(c)
        if (cell != null && cell.toString().trim()) return false
    }
    return true
}

then in your loop:

for (int i = 0; i <= sheet.getLastRowNum(); i++) {
    Row row = sheet.getRow(i)
    if (isRowEmpty(row)) { continue }
    // safe to read cells here
}

to fix the ghost row problem at the source, open your Excel file, select the empty rows below your data, right-click and choose Delete (not just Clear Contents), then save. Excel’s “Clear Contents” leaves formatting behind, which POI still detects as a used row

I made a few script using Apache POI in Katalon Studio to study how to read cells in an Excel sheet which was generated by Number.app on my Mac, which looks like this:

TC1

// Test Cases/TC1

import java.io.InputStream;
import java.io.FileInputStream;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;

InputStream inputStream = new FileInputStream("./sample.xlsx");
Workbook workbook = WorkbookFactory.create(inputStream);
Sheet sheet = workbook.getSheetAt(0);
// print the sheet name
println "sheet name=${sheet.getSheetName()}"

// iterate over the rows contained
for (int rx = sheet.getFirstRowNum(); rx <= sheet.getLastRowNum(); rx++) { // Iterate over each row
	Row row = sheet.getRow(rx);
	for (int cx = row.getFirstCellNum(); cx <= row.getLastCellNum(); cx++) { // Iterate over each cell in the row
		Cell cell = row.getCell(cx)
		StringBuilder sb = new StringBuilder();
		sb.append("(${rx},${cx})");
		sb.append(" \'");
		sb.append(cell.toString())
		sb.append("\'");
		System.out.println(sb.toString()); // Print cell value
	}
	System.out.println(); // New line after each row
}

inputStream.close();

TC1 emitted the following result:

2026-06-17 18:23:33.044 INFO  c.k.katalon.core.main.TestCaseExecutor   - START Test Cases/TC1
sheet name=Sheet1
(0,0) '表1'
(0,1) ''
(0,2) ''
(0,3) ''
(0,4) ''
(0,5) ''
(0,6) ''
(0,7) 'null'

(1,0) 'id'
(1,1) 'name'
(1,2) 'age'
(1,3) 'email'
(1,4) ''
(1,5) ''
(1,6) ''
(1,7) 'null'

(2,0) '1.0'
(2,1) 'foo'
(2,2) '18.0'
(2,3) 'foo@gmail.com'
(2,4) ''
(2,5) ''
(2,6) ''
(2,7) 'null'

(3,0) ''
(3,1) ''
(3,2) ''
(3,3) ''
(3,4) ''
(3,5) ''
(3,6) ''
(3,7) 'null'

(4,0) '2.0'
(4,1) 'bar'
(4,2) ''
(4,3) 'bar@gmail.com'
(4,4) ''
(4,5) ''
(4,6) ''
(4,7) 'null'

(5,0) ''
(5,1) ''
(5,2) ''
(5,3) ''
(5,4) ''
(5,5) ''
(5,6) ''
(5,7) 'null'

(6,0) ''
(6,1) ''
(6,2) ''
(6,3) ''
(6,4) ''
(6,5) ''
(6,6) ''
(6,7) 'null'

(7,0) ''
(7,1) ''
(7,2) ''
(7,3) ''
(7,4) ''
(7,5) ''
(7,6) ''
(7,7) 'null'

(8,0) ''
(8,1) ''
(8,2) ''
(8,3) ''
(8,4) ''
(8,5) ''
(8,6) ''
(8,7) 'null'

(9,0) ''
(9,1) ''
(9,2) ''
(9,3) ''
(9,4) ''
(9,5) ''
(9,6) ''
(9,7) 'null'

(10,0) ''
(10,1) ''
(10,2) ''
(10,3) ''
(10,4) ''
(10,5) ''
(10,6) ''
(10,7) 'null'

(11,0) ''
(11,1) ''
(11,2) ''
(11,3) ''
(11,4) ''
(11,5) ''
(11,6) ''
(11,7) 'null'

(12,0) ''
(12,1) ''
(12,2) ''
(12,3) ''
(12,4) ''
(12,5) ''
(12,6) ''
(12,7) 'null'

(13,0) ''
(13,1) ''
(13,2) ''
(13,3) ''
(13,4) ''
(13,5) ''
(13,6) ''
(13,7) 'null'

(14,0) ''
(14,1) ''
(14,2) ''
(14,3) ''
(14,4) ''
(14,5) ''
(14,6) ''
(14,7) 'null'

(15,0) ''
(15,1) ''
(15,2) ''
(15,3) ''
(15,4) ''
(15,5) ''
(15,6) ''
(15,7) 'null'

(16,0) ''
(16,1) ''
(16,2) ''
(16,3) ''
(16,4) ''
(16,5) ''
(16,6) ''
(16,7) 'null'

(17,0) ''
(17,1) ''
(17,2) ''
(17,3) ''
(17,4) ''
(17,5) ''
(17,6) ''
(17,7) 'null'

(18,0) ''
(18,1) ''
(18,2) ''
(18,3) ''
(18,4) ''
(18,5) ''
(18,6) ''
(18,7) 'null'

(19,0) ''
(19,1) ''
(19,2) ''
(19,3) ''
(19,4) ''
(19,5) ''
(19,6) ''
(19,7) 'null'

(20,0) ''
(20,1) ''
(20,2) ''
(20,3) ''
(20,4) ''
(20,5) ''
(20,6) ''
(20,7) 'null'

(21,0) ''
(21,1) ''
(21,2) ''
(21,3) ''
(21,4) ''
(21,5) ''
(21,6) ''
(21,7) 'null'

(22,0) ''
(22,1) ''
(22,2) ''
(22,3) ''
(22,4) ''
(22,5) ''
(22,6) ''
(22,7) 'null'

TC2

// Test Cases/TC2

import java.io.InputStream;
import java.io.FileInputStream;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;

InputStream inputStream = new FileInputStream("./sample.xlsx");
Workbook workbook = WorkbookFactory.create(inputStream);
Sheet sheet = workbook.getSheetAt(0);
// print the sheet name
println "sheet name=${sheet.getSheetName()}"

// iterate over the rows contained
for (int rx = sheet.getFirstRowNum(); rx <= sheet.getLastRowNum(); rx++) { // Iterate over each row
	Row row = sheet.getRow(rx);
	for (int cx = row.getFirstCellNum(); cx <= row.getLastCellNum(); cx++) { // Iterate over each cell in the row
		Cell cell = row.getCell(cx)
		if (cell != null) {
			StringBuilder sb = new StringBuilder();
			sb.append("(${rx},${cx})");
			sb.append(" \'");
			sb.append(cell.toString())
			sb.append("\'");
			System.out.println(sb.toString()); // Print cell value
		} else {
			System.out.println("(${rx},${cx}) is null")
		}
	}
	System.out.println(); // New line after each row
}

inputStream.close();

TC2 emitted the following

2026-06-17 18:25:46.835 INFO  c.k.katalon.core.main.TestCaseExecutor   - START Test Cases/TC2
sheet name=Sheet1
(0,0) '表1'
(0,1) ''
(0,2) ''
(0,3) ''
(0,4) ''
(0,5) ''
(0,6) ''
(0,7) is null

(1,0) 'id'
(1,1) 'name'
(1,2) 'age'
(1,3) 'email'
(1,4) ''
(1,5) ''
(1,6) ''
(1,7) is null

(2,0) '1.0'
(2,1) 'foo'
(2,2) '18.0'
(2,3) 'foo@gmail.com'
(2,4) ''
(2,5) ''
(2,6) ''
(2,7) is null

(3,0) ''
(3,1) ''
(3,2) ''
(3,3) ''
(3,4) ''
(3,5) ''
(3,6) ''
(3,7) is null

(4,0) '2.0'
(4,1) 'bar'
(4,2) ''
(4,3) 'bar@gmail.com'
(4,4) ''
(4,5) ''
(4,6) ''
(4,7) is null

(5,0) ''
(5,1) ''
(5,2) ''
(5,3) ''
(5,4) ''
(5,5) ''
(5,6) ''
(5,7) is null

(6,0) ''
(6,1) ''
(6,2) ''
(6,3) ''
(6,4) ''
(6,5) ''
(6,6) ''
(6,7) is null

(7,0) ''
(7,1) ''
(7,2) ''
(7,3) ''
(7,4) ''
(7,5) ''
(7,6) ''
(7,7) is null

(8,0) ''
(8,1) ''
(8,2) ''
(8,3) ''
(8,4) ''
(8,5) ''
(8,6) ''
(8,7) is null

(9,0) ''
(9,1) ''
(9,2) ''
(9,3) ''
(9,4) ''
(9,5) ''
(9,6) ''
(9,7) is null

(10,0) ''
(10,1) ''
(10,2) ''
(10,3) ''
(10,4) ''
(10,5) ''
(10,6) ''
(10,7) is null

(11,0) ''
(11,1) ''
(11,2) ''
(11,3) ''
(11,4) ''
(11,5) ''
(11,6) ''
(11,7) is null

(12,0) ''
(12,1) ''
(12,2) ''
(12,3) ''
(12,4) ''
(12,5) ''
(12,6) ''
(12,7) is null

(13,0) ''
(13,1) ''
(13,2) ''
(13,3) ''
(13,4) ''
(13,5) ''
(13,6) ''
(13,7) is null

(14,0) ''
(14,1) ''
(14,2) ''
(14,3) ''
(14,4) ''
(14,5) ''
(14,6) ''
(14,7) is null

(15,0) ''
(15,1) ''
(15,2) ''
(15,3) ''
(15,4) ''
(15,5) ''
(15,6) ''
(15,7) is null

(16,0) ''
(16,1) ''
(16,2) ''
(16,3) ''
(16,4) ''
(16,5) ''
(16,6) ''
(16,7) is null

(17,0) ''
(17,1) ''
(17,2) ''
(17,3) ''
(17,4) ''
(17,5) ''
(17,6) ''
(17,7) is null

(18,0) ''
(18,1) ''
(18,2) ''
(18,3) ''
(18,4) ''
(18,5) ''
(18,6) ''
(18,7) is null

(19,0) ''
(19,1) ''
(19,2) ''
(19,3) ''
(19,4) ''
(19,5) ''
(19,6) ''
(19,7) is null

(20,0) ''
(20,1) ''
(20,2) ''
(20,3) ''
(20,4) ''
(20,5) ''
(20,6) ''
(20,7) is null

(21,0) ''
(21,1) ''
(21,2) ''
(21,3) ''
(21,4) ''
(21,5) ''
(21,6) ''
(21,7) is null

(22,0) ''
(22,1) ''
(22,2) ''
(22,3) ''
(22,4) ''
(22,5) ''
(22,6) ''
(22,7) is null

2026-06-17 18:25:49.885 INFO  c.k.katalon.core.main.TestCaseExecutor   - END Test Cases/TC2

TC3

// Test Cases/TC3

import java.io.InputStream;
import java.io.FileInputStream;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.util.CellAddress;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;

InputStream inputStream = new FileInputStream("./sample.xlsx");
Workbook workbook = WorkbookFactory.create(inputStream);
Sheet sheet = workbook.getSheetAt(0);
// print the sheet name
println "sheet name=${sheet.getSheetName()}"

// iterate over the rows contained
for (Row row : sheet) { // Iterate over each row
	for (Cell cell : row) { // Iterate over each cell in the row
		CellAddress cellAddress = cell.getAddress();
		StringBuilder sb = new StringBuilder();
		sb.append(cellAddress.formatAsR1C1String());
		sb.append(" \'");
		sb.append(cell.toString())
		sb.append("\'");
		System.out.println(sb.toString()); // Print cell value
	}
	System.out.println(); // New line after each row
}

inputStream.close();

TC3 emitted the following

2026-06-17 18:27:38.144 INFO  c.k.katalon.core.main.TestCaseExecutor   - START Test Cases/TC3
sheet name=Sheet1
R1C1 '表1'
R1C2 ''
R1C3 ''
R1C4 ''
R1C5 ''
R1C6 ''
R1C7 ''

R2C1 'id'
R2C2 'name'
R2C3 'age'
R2C4 'email'
R2C5 ''
R2C6 ''
R2C7 ''

R3C1 '1.0'
R3C2 'foo'
R3C3 '18.0'
R3C4 'foo@gmail.com'
R3C5 ''
R3C6 ''
R3C7 ''

R4C1 ''
R4C2 ''
R4C3 ''
R4C4 ''
R4C5 ''
R4C6 ''
R4C7 ''

R5C1 '2.0'
R5C2 'bar'
R5C3 ''
R5C4 'bar@gmail.com'
R5C5 ''
R5C6 ''
R5C7 ''

R6C1 ''
R6C2 ''
R6C3 ''
R6C4 ''
R6C5 ''
R6C6 ''
R6C7 ''

R7C1 ''
R7C2 ''
R7C3 ''
R7C4 ''
R7C5 ''
R7C6 ''
R7C7 ''

R8C1 ''
R8C2 ''
R8C3 ''
R8C4 ''
R8C5 ''
R8C6 ''
R8C7 ''

R9C1 ''
R9C2 ''
R9C3 ''
R9C4 ''
R9C5 ''
R9C6 ''
R9C7 ''

R10C1 ''
R10C2 ''
R10C3 ''
R10C4 ''
R10C5 ''
R10C6 ''
R10C7 ''

R11C1 ''
R11C2 ''
R11C3 ''
R11C4 ''
R11C5 ''
R11C6 ''
R11C7 ''

R12C1 ''
R12C2 ''
R12C3 ''
R12C4 ''
R12C5 ''
R12C6 ''
R12C7 ''

R13C1 ''
R13C2 ''
R13C3 ''
R13C4 ''
R13C5 ''
R13C6 ''
R13C7 ''

R14C1 ''
R14C2 ''
R14C3 ''
R14C4 ''
R14C5 ''
R14C6 ''
R14C7 ''

R15C1 ''
R15C2 ''
R15C3 ''
R15C4 ''
R15C5 ''
R15C6 ''
R15C7 ''

R16C1 ''
R16C2 ''
R16C3 ''
R16C4 ''
R16C5 ''
R16C6 ''
R16C7 ''

R17C1 ''
R17C2 ''
R17C3 ''
R17C4 ''
R17C5 ''
R17C6 ''
R17C7 ''

R18C1 ''
R18C2 ''
R18C3 ''
R18C4 ''
R18C5 ''
R18C6 ''
R18C7 ''

R19C1 ''
R19C2 ''
R19C3 ''
R19C4 ''
R19C5 ''
R19C6 ''
R19C7 ''

R20C1 ''
R20C2 ''
R20C3 ''
R20C4 ''
R20C5 ''
R20C6 ''
R20C7 ''

R21C1 ''
R21C2 ''
R21C3 ''
R21C4 ''
R21C5 ''
R21C6 ''
R21C7 ''

R22C1 ''
R22C2 ''
R22C3 ''
R22C4 ''
R22C5 ''
R22C6 ''
R22C7 ''

R23C1 ''
R23C2 ''
R23C3 ''
R23C4 ''
R23C5 ''
R23C6 ''
R23C7 ''

2026-06-17 18:27:41.028 INFO  c.k.katalon.core.main.TestCaseExecutor   - END Test Cases/TC3

My opinion

TC3 is better than TC1 and TC2.

TC3 uses Sheet.iterator() and Row.interator(). These methods gives you a logical view without NULL objects. Using these methods, you would encounter no null; so you would not get NullPointerException.

TC1 and TC2 use getFirstRowNum(), getLastRowNum(), getRow() methods of Sheet class; getFistCellNum(), getLastCellNum(), getCell() methods of Row class. You would be tempted to use them in a loop of for (int i = theFirstIndex; i < theLastIndex; i**) { Cell cell = row.getCell(i) }. The row.getCell(i) is quite liketly to return null. When you use these methods, you have to be very careful about NULL.

I would recommend you Not to use getFirstRowNum() and the following INDEX-based access to the physical view.

You should prefer the builtin iterator() methods.

In the previous post of mine, I created sample.xlsx file by exporting the file out of Number.app of Mac. I realized that the file could be quite different from the one created by Microsoft Excel on Windows PC. So I created a native Book1.xlsx file using MS Excel on Windows; and ran the same set of Test Cases TC1, TC2 and TC3. The result was quite different. Let me report it.

The Book1.xlsx looked as this:

TC1 result using Book1.xlsx

2026-06-17 20:27:17.942 INFO  c.k.katalon.core.main.TestCaseExecutor   - START Test Cases/TC1
sheet name=Sheet1
(0,0) 'id'
(0,1) 'name'
(0,2) 'age'
(0,3) 'email'
(0,4) 'null'

(1,0) '1.0'
(1,1) 'foo'
(1,2) '18.0'
(1,3) 'foo@gmail.com'
(1,4) 'null'

2026-06-17 20:27:23.743 ERROR c.k.katalon.core.main.TestCaseExecutor   - ❌ Test Cases/TC1 FAILED.
Reason:
java.lang.NullPointerException: Cannot invoke method getFirstCellNum() on null object
	at TC1.run(TC1:20)
	at com.kms.katalon.core.main.ScriptEngine.run(ScriptEngine.java:194)
	at com.kms.katalon.core.main.ScriptEngine.runScriptAsRawText(ScriptEngine.java:119)
	at com.kms.katalon.core.main.TestCaseExecutor.runScript(TestCaseExecutor.java:502)
	at com.kms.katalon.core.main.TestCaseExecutor.doExecute(TestCaseExecutor.java:493)
	at com.kms.katalon.core.main.TestCaseExecutor.processExecutionPhase(TestCaseExecutor.java:472)
	at com.kms.katalon.core.main.TestCaseExecutor.accessMainPhase(TestCaseExecutor.java:464)
	at com.kms.katalon.core.main.TestCaseExecutor.execute(TestCaseExecutor.java:334)
	at com.kms.katalon.core.main.TestCaseMain.runTestCase(TestCaseMain.java:151)
	at com.kms.katalon.core.main.TestCaseMain.runTestCase(TestCaseMain.java:139)
	at TempTestCase1781695623158.run(TempTestCase1781695623158.groovy:30)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)

2026-06-17 20:27:23.767 ERROR c.k.katalon.core.main.TestCaseExecutor   - ❌ Test Cases/TC1 FAILED.
Reason:
java.lang.NullPointerException: Cannot invoke method getFirstCellNum() on null object
	at TC1.run(TC1:20)
	at com.kms.katalon.core.main.ScriptEngine.run(ScriptEngine.java:194)
	at com.kms.katalon.core.main.ScriptEngine.runScriptAsRawText(ScriptEngine.java:119)
	at com.kms.katalon.core.main.TestCaseExecutor.runScript(TestCaseExecutor.java:502)
	at com.kms.katalon.core.main.TestCaseExecutor.doExecute(TestCaseExecutor.java:493)
	at com.kms.katalon.core.main.TestCaseExecutor.processExecutionPhase(TestCaseExecutor.java:472)
	at com.kms.katalon.core.main.TestCaseExecutor.accessMainPhase(TestCaseExecutor.java:464)
	at com.kms.katalon.core.main.TestCaseExecutor.execute(TestCaseExecutor.java:334)
	at com.kms.katalon.core.main.TestCaseMain.runTestCase(TestCaseMain.java:151)
	at com.kms.katalon.core.main.TestCaseMain.runTestCase(TestCaseMain.java:139)
	at TempTestCase1781695623158.run(TempTestCase1781695623158.groovy:30)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)

2026-06-17 20:27:23.771 INFO  c.k.katalon.core.main.TestCaseExecutor   - END Test Cases/TC1

TC2 result

2026-06-17 20:28:26.766 INFO  c.k.katalon.core.main.TestCaseExecutor   - START Test Cases/TC2
sheet name=Sheet1
(0,0) 'id'
(0,1) 'name'
(0,2) 'age'
(0,3) 'email'
(0,4) is null

(1,0) '1.0'
(1,1) 'foo'
(1,2) '18.0'
(1,3) 'foo@gmail.com'
(1,4) is null

2026-06-17 20:28:32.804 ERROR c.k.katalon.core.main.TestCaseExecutor   - ❌ Test Cases/TC2 FAILED.
Reason:
java.lang.NullPointerException: Cannot invoke method getFirstCellNum() on null object
	at TC2.run(TC2:20)
	at com.kms.katalon.core.main.ScriptEngine.run(ScriptEngine.java:194)
	at com.kms.katalon.core.main.ScriptEngine.runScriptAsRawText(ScriptEngine.java:119)
	at com.kms.katalon.core.main.TestCaseExecutor.runScript(TestCaseExecutor.java:502)
	at com.kms.katalon.core.main.TestCaseExecutor.doExecute(TestCaseExecutor.java:493)
	at com.kms.katalon.core.main.TestCaseExecutor.processExecutionPhase(TestCaseExecutor.java:472)
	at com.kms.katalon.core.main.TestCaseExecutor.accessMainPhase(TestCaseExecutor.java:464)
	at com.kms.katalon.core.main.TestCaseExecutor.execute(TestCaseExecutor.java:334)
	at com.kms.katalon.core.main.TestCaseMain.runTestCase(TestCaseMain.java:151)
	at com.kms.katalon.core.main.TestCaseMain.runTestCase(TestCaseMain.java:139)
	at TempTestCase1781695692584.run(TempTestCase1781695692584.groovy:30)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)

2026-06-17 20:28:32.828 ERROR c.k.katalon.core.main.TestCaseExecutor   - ❌ Test Cases/TC2 FAILED.
Reason:
java.lang.NullPointerException: Cannot invoke method getFirstCellNum() on null object
	at TC2.run(TC2:20)
	at com.kms.katalon.core.main.ScriptEngine.run(ScriptEngine.java:194)
	at com.kms.katalon.core.main.ScriptEngine.runScriptAsRawText(ScriptEngine.java:119)
	at com.kms.katalon.core.main.TestCaseExecutor.runScript(TestCaseExecutor.java:502)
	at com.kms.katalon.core.main.TestCaseExecutor.doExecute(TestCaseExecutor.java:493)
	at com.kms.katalon.core.main.TestCaseExecutor.processExecutionPhase(TestCaseExecutor.java:472)
	at com.kms.katalon.core.main.TestCaseExecutor.accessMainPhase(TestCaseExecutor.java:464)
	at com.kms.katalon.core.main.TestCaseExecutor.execute(TestCaseExecutor.java:334)
	at com.kms.katalon.core.main.TestCaseMain.runTestCase(TestCaseMain.java:151)
	at com.kms.katalon.core.main.TestCaseMain.runTestCase(TestCaseMain.java:139)
	at TempTestCase1781695692584.run(TempTestCase1781695692584.groovy:30)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)

2026-06-17 20:28:32.844 INFO  c.k.katalon.core.main.TestCaseExecutor   - END Test Cases/TC2

TC3 result

2026-06-17 20:29:26.197 INFO  c.k.katalon.core.main.TestCaseExecutor   - START Test Cases/TC3
sheet name=Sheet1
R1C1 'id'
R1C2 'name'
R1C3 'age'
R1C4 'email'

R2C1 '1.0'
R2C2 'foo'
R2C3 '18.0'
R2C4 'foo@gmail.com'

R4C1 '2.0'
R4C2 'bar'
R4C4 'bar@gmail.com'

2026-06-17 20:29:32.311 INFO  c.k.katalon.core.main.TestCaseExecutor   - END Test Cases/TC3

My opinion

TC1 and TC2 failed due to NullPointerException. But TC3 passed.

Based on this experiment, I would argue that you should use Sheet.iterator<Row>() and Row.iterator<Cell>() methods. These methods takes care of possible nulls as Rows and Cells for you.

You had better refrain from `Sheet.getFirstRowNum()’ and other getter methods by index if possible. If you are to stick to these methods, you are oblidged to take enough care of nulls for yourself.

I published the project in a repository on GitHub:

Really informative post

Did you try any of the solution posted below?

i was handling it in every test case the custom keyword thing is quite helpful, as i might need to change logic maybe show an error or by pass so can be done here. thanks

Keyword is reusable and can be updated once and fixed at eveywhere!