Horizontal Data Tables in Cucumber JVM

1. Introduction to Horizontal Data Tables

Horizontal Data Tables are one of the most practical ways to pass structured business data from a Gherkin feature file into a Cucumber JVM Step Definition. They are especially useful when one scenario needs several related values at the same time, such as username, password, and role, or product, quantity, and amount. Instead of spreading those values across many separate steps, a Data Table lets the feature file present the data in a compact and readable table.

A Data Table is different from a Scenario Outline. A Scenario Outline executes the same scenario multiple times by replacing placeholders with values from an Examples table. A Data Table normally executes the scenario once and passes the whole table to the Step Definition as structured data. This distinction is important because many beginners confuse the two. If the goal is repeated execution, use Scenario Outline. If the goal is to pass structured input into one execution, use a Data Table.

Horizontal Data Tables are called horizontal because the first row contains headers arranged across the table. Each following row contains values under those headers. This format is very natural for records, lists, and object-like data. A user record may have Username, Password, and Role columns. An employee record may have Name, Department, and Salary. An order row may have Product, Quantity, Price, and Status.

When used well, Horizontal Data Tables make feature files more business-friendly and reduce repetitive Gherkin. They also make Step Definitions cleaner because Cucumber passes the table as a DataTable object that can be converted into Java collections such as List<Map<String, String>>. In larger frameworks, table rows can be mapped to domain objects or POJOs for stronger typing and better maintainability.

2. What Is a Data Table?

Structured Data Inside Gherkin

A Data Table is a Gherkin table written directly under a step. It allows the step to receive structured tabular data. The table is part of the step, not a separate scenario execution mechanism. Cucumber reads the table and passes it to the matching Step Definition.

Given the following user details
  | Username | Password | Role  |
  | admin    | admin123 | Admin |

The Step Definition can receive the table as an io.cucumber.datatable.DataTable object:

@Given("the following user details")
public void userDetails(DataTable table) {
    // use table data here
}

The DataTable object contains all rows and columns from the feature file. The automation code can then convert it into lists, maps, or custom objects depending on the need.

3. What Is a Horizontal Data Table?

A Horizontal Data Table is a table where the first row contains column headers, and each subsequent row contains values for those columns. The headers describe what each value means. This makes the table self-descriptive and easy to map into Java objects.

| Username | Password | Role  |
| admin    | admin123 | Admin |

Here, Username, Password, and Role are headers. The next row contains the values admin, admin123, and Admin. Because the headers are arranged horizontally across the first row, this is a Horizontal Data Table.

This format is ideal when you want to represent one or more records. Each row after the header can be treated as one record. In Java, the common conversion is a list of maps, where each row is a map and each header is a key.

4. Why Use Horizontal Data Tables?

Related Values Belong Together

Horizontal Data Tables are useful when a scenario needs multiple related values. For example, user details normally include username, password, and role. Product details may include product name, quantity, and price. Address details may include street, city, state, and zip code. Keeping these values together in one table makes the feature file easier to read.

Without a Data Table, the same user details may require several separate steps:

Given the username is "admin"
And the password is "admin123"
And the role is "Admin"

With a Horizontal Data Table, the values are grouped naturally:

Given the following user details
  | Username | Password | Role  |
  | admin    | admin123 | Admin |

The second version is cleaner because it presents the data as a record. The step says what the table represents, and the table shows the values.

5. Horizontal Data Table Structure

The structure of a Horizontal Data Table is simple. The first row contains headers. The rows below contain data. Every value belongs to the header above it.

| Column1 | Column2 | Column3 |
| Value1  | Value2  | Value3  |
| Value4  | Value5  | Value6  |

In this structure, the first data row can be read as Column1 = Value1, Column2 = Value2, and Column3 = Value3. The second data row can be read as Column1 = Value4, Column2 = Value5, and Column3 = Value6.

This is why meaningful headers are important. A header named Username tells the reader what the column means. A header named Value1 does not.

6. Execution Flow

When Cucumber executes a step with a Data Table, it reads the table as part of the step. It creates a DataTable object and passes it to the Step Definition method. The method then converts the table into a Java structure that is convenient for the framework.

Feature File
  |
  v
Read Data Table
  |
  v
Convert to DataTable Object
  |
  v
Transform into Java Collection
  |
  v
Use Values in Step Definition

This flow is different from Scenario Outline. A Data Table does not automatically create multiple scenario executions. It passes the full table to one step in one scenario execution unless your Java code explicitly loops over rows and performs multiple actions.

7. Basic Data Table Example

Here is a basic login example using a Horizontal Data Table:

Feature: Login

Scenario: Login with user details
  Given the following user details
    | Username | Password | Role  |
    | admin    | admin123 | Admin |

The Step Definition receives the table:

@Given("the following user details")
public void userDetails(DataTable table) {
    // DataTable contains all table rows and columns
}

At this point, the data is available, but it still needs to be converted into a convenient Java form. The most common option for a horizontal table is asMaps().

8. The DataTable Object

Cucumber's Table Representation

Cucumber provides the io.cucumber.datatable.DataTable class to represent table data. This object gives methods for converting the table into different Java structures. The right conversion depends on the shape of the table and how the step wants to use the data.

import io.cucumber.datatable.DataTable;

@Given("the following user details")
public void userDetails(DataTable table) {
    // table conversion happens here
}

For Horizontal Data Tables with headers, asMaps(String.class, String.class) is usually the most readable conversion. It allows you to access values by header name instead of by numeric index.

9. Converting to List of Maps

The most common way to read a Horizontal Data Table is to convert it into a List<Map<String, String>>. Each row becomes one map. Each column header becomes a key in that map.

@Given("the following user details")
public void userDetails(DataTable table) {
    List<Map<String, String>> users =
        table.asMaps(String.class, String.class);
}

If the table has one data row, the list contains one map. If the table has three data rows, the list contains three maps. This makes the structure flexible for both single records and multiple records.

The advantage of maps is readability. Instead of remembering that index 0 is Username and index 1 is Password, the code can use header names directly.

10. Accessing Values from a Single Row

For a table with one data row, access the first map using users.get(0), then read values by column name.

Given the following user details
  | Username | Password | Role  |
  | admin    | admin123 | Admin |
List<Map<String, String>> users =
    table.asMaps(String.class, String.class);

String username = users.get(0).get("Username");
String password = users.get(0).get("Password");
String role = users.get(0).get("Role");

The output values are admin, admin123, and Admin. This is clear because the Java code uses the same header names that appear in the feature file.

Header names must match exactly. If the feature file says UserName but the Java code asks for Username, the result may be null. Consistent naming prevents this problem.

11. Working with Multiple Rows

Horizontal Data Tables can contain multiple rows. This is useful when one scenario needs to create several users, products, employees, or records as setup data.

Given the following users
  | Username | Password | Role     |
  | admin    | admin123 | Admin    |
  | john     | pass123  | Customer |
  | peter    | demo123  | Manager  |

The Step Definition can loop through the list of maps:

List<Map<String, String>> users =
    table.asMaps(String.class, String.class);

for (Map<String, String> user : users) {
    System.out.println(user.get("Username"));
}

The output is admin, john, and peter. Each row becomes one map, and the loop processes each record. This design is useful for setup scenarios where several related records are needed before the main action occurs.

12. Iterating Through Rows Safely

Read Each Row as a Record

When iterating through rows, treat each map as one complete record. Extract values using meaningful keys and pass them to services, Page Objects, or factories.

for (Map<String, String> row : users) {
    String username = row.get("Username");
    String password = row.get("Password");
    String role = row.get("Role");

    userService.createUser(username, password, role);
}

Avoid placing too much logic directly inside the Step Definition. If the row needs validation, transformation, or object creation, delegate that work to a helper or factory. The Step Definition should stay thin even when the table contains several rows.

13. Converting Rows to Custom Objects

In larger frameworks, repeatedly reading maps can become noisy. A better approach is to convert rows into custom objects, often called POJOs. This improves type safety, readability, and maintainability.

public class User {
    private String username;
    private String password;
    private String role;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }
}

The row can be mapped manually:

Map<String, String> row = users.get(0);

User user = new User();
user.setUsername(row.get("Username"));
user.setPassword(row.get("Password"));
user.setRole(row.get("Role"));

This is preferred in larger frameworks because the rest of the automation code can work with a User object instead of a loose map of strings.

14. Manual Mapping vs Custom Transformers

Manual mapping is simple and easy to understand, but it can become repetitive if many steps convert the same table shape. Cucumber also supports table transformation patterns that can map Data Tables into custom types more elegantly. Whether you use manual mapping or custom transformers depends on framework size and team comfort.

For a small project, manual mapping may be enough. For an enterprise framework with repeated User, Employee, Product, Address, or Order tables, custom transformation logic can reduce duplication and improve consistency.

The main rule is the same: keep conversion logic centralized when it is reused. Do not repeat the same map-to-object conversion in many Step Definitions.

15. Horizontal vs Vertical Data Tables

Choosing the Right Shape

Horizontal and Vertical Data Tables represent data differently. A Horizontal Data Table has headers in the first row and values in later rows. A Vertical Data Table usually has field names in the first column and values in the second column.

// Horizontal
| Username | Password |
| admin    | admin123 |

// Vertical
| Username | admin    |
| Password | admin123 |

Horizontal tables are ideal for records, lists, and multiple objects. Vertical tables are often easier for a single object with many fields, especially when the table would become too wide horizontally.

If you need a list of users, products, or employees, horizontal is usually better. If you need one configuration-like object with many properties, vertical may be easier to read.

16. Data Table vs Scenario Outline

A Data Table and Scenario Outline both use table-like syntax, but they behave differently. A Data Table passes structured data into one scenario execution. A Scenario Outline executes the same scenario once per Examples row.

Aspect Data Table Scenario Outline
Execution count One execution Multiple executions
Data use Structured input Different datasets
Java handling DataTable object Step parameters
Best use Passing objects or lists Repeating scenario with different data

If Admin, Customer, and Manager should run as three separate login tests, use Scenario Outline. If one scenario needs a list of three users as setup data, use a Data Table.

17. Real-Time Example: Create Employee

An employee creation scenario is a good example of a Horizontal Data Table because employee details naturally form a record.

Scenario: Create Employee
  Given the employee details
    | Name | Department | Salary |
    | John | IT         | 80000  |

The Step Definition can read the table as maps:

@Given("the employee details")
public void employeeDetails(DataTable table) {
    List<Map<String, String>> employees =
        table.asMaps(String.class, String.class);

    String name = employees.get(0).get("Name");
    String department = employees.get(0).get("Department");
    String salary = employees.get(0).get("Salary");

    employeeService.createEmployee(name, department, salary);
}

In a more mature framework, salary may be converted to BigDecimal or int, and the row may become an Employee object before being passed to the service.

18. Advantages of Horizontal Data Tables

Horizontal Data Tables are easy to read when data naturally fits into columns. They reduce multiple Gherkin steps, support structured input, and are easy to convert into Java collections. They work well with POJOs and make feature files more business-friendly when the data set is small and meaningful.

  • Easy to read for record-like data
  • Reduces repeated Gherkin steps
  • Supports structured business input
  • Converts cleanly to List of Maps
  • Works well with domain objects and POJOs
  • Improves maintainability for related values
  • Makes setup data visible in the scenario

19. Limitations of Horizontal Data Tables

Horizontal Data Tables execute within one scenario. They are not the right choice when the goal is repeated scenario execution. If each data row should be reported as a separate test case, Scenario Outline is usually better.

Horizontal tables can also become hard to read when they contain too many columns. A table with fifteen fields may stretch across the page and become difficult to scan. In that case, a Vertical Data Table or a custom object setup step may be clearer.

Large tables reduce readability. Complex nested data may require custom transformation logic. Sensitive data should not be placed in feature files. Technical configuration should come from framework settings, not Data Tables.

20. Common Mistake: Missing Header Row

A Horizontal Data Table should have a header row if you plan to use asMaps(). Without headers, the table is not self-descriptive and cannot be accessed clearly by column name.

// Poor
| admin | admin123 |

// Better
| Username | Password |
| admin    | admin123 |

Headers make the table readable and allow Java code to access values by meaningful keys.

21. Common Mistake: Incorrect Column Names

Column names must match exactly between the feature file and Java code. If the table header is UserName and Java calls row.get("Username"), the lookup will not return the expected value.

| UserName | Password |
| admin    | admin123 |

String username = row.get("Username"); // mismatch

Use consistent naming and avoid unnecessary variations such as UserName, Username, user_name, and User Name across different feature files.

22. Common Mistake: Using Data Tables for Data-Driven Testing

A Data Table should not be used when the intention is to execute the same scenario multiple times with different data sets. A Data Table passes all rows to one Step Definition in one scenario execution. It does not automatically create separate scenario executions.

If Admin, Customer, and Manager should each be separate test executions, use Scenario Outline:

Scenario Outline: Login by role
  Given the user logs in as "<Role>"

Examples:
  | Role     |
  | Admin    |
  | Customer |
  | Manager  |

Use Data Tables when one execution needs structured input. Use Scenario Outline when multiple executions are required.

23. Common Mistake: Storing Large Datasets

Large Data Tables make feature files hard to read. Hundreds of rows should not be stored directly in Gherkin. If the data set is large, frequently changing, or generated, external sources such as CSV, Excel, JSON, databases, or APIs are usually better.

Feature files should remain readable documentation. A Data Table should clarify the scenario, not overwhelm it.

24. Best Practices for Horizontal Data Tables

Use Horizontal Data Tables for structured business data that belongs together. Keep headers meaningful. Prefer table.asMaps(String.class, String.class) when the table has headers. Convert rows into domain objects in larger frameworks. Keep tables concise and business-readable.

Use Scenario Outline instead of Data Tables for repeated executions with different datasets. Avoid technical configuration data in feature files. Keep sensitive information out of Data Tables. Keep Step Definitions thin by delegating table processing to services or factories when needed.

  • Use headers that describe business meaning.
  • Use horizontal tables for records and lists.
  • Prefer asMaps() for header-based access.
  • Map rows to POJOs in larger frameworks.
  • Keep tables small and readable.
  • Do not use Data Tables for repeated scenario execution.
  • Move large datasets to external sources.

25. Data Tables and Thin Step Definitions

Do Not Put Too Much Mapping Logic in Steps

Data Tables can tempt developers to put a lot of parsing and object creation inside Step Definitions. This makes steps fat. A better pattern is to keep the step thin and delegate mapping to a factory or transformer.

@Given("the following users")
public void followingUsers(DataTable table) {
    List<User> users = userTableMapper.toUsers(table);
    userService.createUsers(users);
}

This keeps the Step Definition readable. The table mapper owns conversion. The service owns user creation. Each layer has one responsibility.

26. Validating Horizontal Data Table Content

Fail Fast on Missing or Invalid Fields

Horizontal Data Tables become more reliable when their content is validated before the framework uses it. A table may look correct in the feature file but still contain a missing column, empty value, invalid number, unsupported role, or spelling mismatch. If the Step Definition reads values without validation, the failure may appear later in the Page Object or service layer, making the real problem harder to understand.

A good mapper or helper can validate required headers such as Username, Password, Role, Product, Quantity, or Amount. It can also validate required values and convert numeric fields safely. For example, if Quantity must be an integer greater than zero, the mapper should detect invalid table data and throw a clear error. This is better than allowing an empty string to reach a cart service and fail with a confusing exception.

Validation does not mean adding heavy logic inside Step Definitions. The Step Definition should still stay thin. The validation belongs in a table mapper, fixture builder, or domain factory. The Step Definition calls the mapper, and the mapper returns clean objects or fails with a useful message.

@Given("the cart contains the following products")
public void cartContainsProducts(DataTable table) {
    List<Product> products = productTableMapper.toProducts(table);
    cartService.addProducts(products);
}

This pattern keeps Data Table handling professional. The feature file remains readable, the step remains short, and invalid table data is caught early with meaningful feedback.

27. Horizontal Data Tables in Large Frameworks

In large Cucumber JVM frameworks, Horizontal Data Tables should follow shared standards. Teams should agree on common header names, table shapes, object mapping patterns, and where table conversion logic belongs. Without standards, one feature may use Username, another may use UserName, another may use User, and another may use LoginId for the same concept. This inconsistency makes table processing harder and encourages duplicate mapping code.

A practical approach is to create reusable mappers for common tables. UserTableMapper, ProductTableMapper, AddressTableMapper, EmployeeTableMapper, and OrderTableMapper can convert DataTable objects into domain objects. This keeps Step Definitions thin and gives the team one place to maintain conversion rules. If the Product table gains a new optional column, the product mapper can handle it consistently.

Teams should also avoid using Horizontal Data Tables as a dumping ground for every field available in an application form. A table should contain the data relevant to the scenario. If a user profile has twenty fields but the scenario only cares about Role and Status, the table should not include all twenty fields. Unnecessary columns make scenarios harder to read and create maintenance work when fields change.

Large frameworks also need a policy for sensitive values. Passwords, tokens, API keys, production identifiers, and personal data should not be stored directly in feature files. A table can contain a business alias such as AdminUser or PremiumCustomer, while the framework resolves secure data from a protected source.

When Data Tables are standardized, they become easier to read and reuse. When every team invents its own table structure, the framework becomes inconsistent even if individual scenarios still pass.

28. Data Tables and Business Readability

A good Data Table should make the scenario easier to understand. The table should show data that the business cares about. If the table contains framework-only fields, internal IDs, driver paths, timeouts, or environment URLs, it weakens readability.

Use values that stakeholders can discuss. Role, Department, Salary, Product, Quantity, Amount, City, and Status are business-readable. ElementId, CssSelector, DriverPath, and RetryCount are technical values and usually belong elsewhere.

29. Data Tables and Test Data Management

Horizontal Data Tables can work with test data management by describing the business data needed for a scenario while the framework resolves technical details. For example, the table may say Role = Admin, and the framework can resolve the actual username and password from secure test data storage.

This avoids exposing credentials in feature files and keeps the scenario business-focused. The table provides the business intent, and the automation framework handles the technical data behind the scenes.

30. Real-Time Example: Multiple Products

An order setup scenario may need multiple products in the cart. A Horizontal Data Table is suitable because each row represents one product record.

Given the cart contains the following products
  | Product | Quantity | Price  |
  | Laptop  | 1        | 1000   |
  | Mouse   | 2        | 25     |
  | Monitor | 1        | 300    |
@Given("the cart contains the following products")
public void cartContainsProducts(DataTable table) {
    List<Map<String, String>> rows =
        table.asMaps(String.class, String.class);

    for (Map<String, String> row : rows) {
        cartService.addProduct(
            row.get("Product"),
            Integer.parseInt(row.get("Quantity")),
            new BigDecimal(row.get("Price"))
        );
    }
          }

In a refined framework, the parsing would move to a mapper so the Step Definition remains thinner.

31. When Not to Use Horizontal Data Tables

Do not use a Horizontal Data Table when each row should be a separate scenario execution. Use Scenario Outline for that. Do not use horizontal tables when the table becomes too wide to read. Use Vertical Data Tables or structured setup helpers instead. Do not use Data Tables for huge datasets or technical configuration.

The table should make the scenario clearer. If it makes the scenario harder to read, choose another approach.

32. Code Review Checklist

Questions Before Committing

Before committing a Horizontal Data Table, check whether the table is truly structured business input for one scenario execution. Confirm that headers are meaningful, values are readable, and the Step Definition handles the table cleanly. Check whether a Scenario Outline would be more appropriate.

  • Does the table pass structured data to one scenario execution?
  • Are headers meaningful and consistent?
  • Are column names matched exactly in Java code?
  • Is the table small enough to read?
  • Should rows be mapped to a POJO?
  • Should repeated execution use Scenario Outline instead?
  • Is sensitive or technical data excluded?
  • Is table parsing kept out of fat Step Definitions?

33. Interview-Ready Summary

Short Explanation for Interviews

A Horizontal Data Table is a Gherkin table where the first row contains column headers and the remaining rows contain data. Cucumber passes the table to the Step Definition as a DataTable object. The most common way to read it is with asMaps(String.class, String.class), where each row becomes a map keyed by the column headers.

Horizontal Data Tables are ideal for passing structured business data into a single scenario execution. They are useful for user details, product details, address information, employee records, customer profiles, and order setup. For larger frameworks, mapping rows to POJOs improves readability, type safety, and maintainability.

  • Horizontal tables use headers in the first row.
  • DataTable is received by the Step Definition.
  • asMaps() is commonly used for header-based access.
  • Each row can represent one record.
  • Use Scenario Outline for multiple executions instead.

34. Golden Rule

The golden rule is simple: use Horizontal Data Tables when a single scenario needs structured business data. If the goal is to execute the same scenario multiple times with different datasets, use a Scenario Outline instead.

When Horizontal Data Tables are used well, feature files become cleaner, Step Definitions receive structured input, and Cucumber JVM frameworks become easier to maintain without sacrificing business readability.