Typed Data Tables
The default LIST_OF_OBJECT_PARAMS data-table mode — Gherkin data tables generate type-safe inner classes with typed accessors. No Cucumber dependency.
Source: examples/common-use-cases/example-1 on GitHub.
What this demonstrates
- Data tables generate inner
Paramclasses (named after the last word of the step text +Param) - Column headers become typed fields with automatic type inference
- Per-column inference:
Integer,Double,Boolean,String, … - Readable headers (e.g.
unit price) are sanitised to Java field names (unitPrice) - Each row becomes a
new XxxParam(...)call insideList.of(...) - Multiple data tables in the same feature produce separate inner classes
- The concrete test reads rows through the typed accessors — no string parsing
The Gherkin
Given my cart contains the following products:
| name | qty | unit price | in stock |
| Wireless Headphones | 1 | 59.99 | true |
| Coffee Beans 1kg | 3 | 12.50 | true |
| USB-C Cable | 2 | 8.99 | false |
The spec, ShoppingCart.specb, sits next to its marker class in src/test/java, so the marker needs only a bare
@Gherkin2JUnit — the processor finds the spec by convention.
The generated inner class
public static class ProductsParam {
private final String name;
private final Integer qty;
private final Double unitPrice;
private final Boolean inStock;
public ProductsParam(String name, Integer qty, Double unitPrice, Boolean inStock) {
this.name = name;
this.qty = qty;
this.unitPrice = unitPrice;
this.inStock = inStock;
}
public String name() { return this.name; }
public Integer qty() { return this.qty; }
public Double unitPrice() { return this.unitPrice; }
public Boolean inStock() { return this.inStock; }
}
The generated step method and call site
// Step method signature
public abstract void myCartContainsTheFollowingProducts(List<ProductsParam> products);
// Generated call site
myCartContainsTheFollowingProducts(
List.of(
new ProductsParam("Wireless Headphones", 1, 59.99, true),
new ProductsParam("Coffee Beans 1kg", 3, 12.50, true),
new ProductsParam("USB-C Cable", 2, 8.99, false)
));
Implementing the steps
The concrete ShoppingCartTest extends the generated ShoppingCartScenarios and works with the rows as typed objects —
product.qty() is an Integer, product.inStock() a Boolean:
public class ShoppingCartTest extends ShoppingCartScenarios {
private List<ProductsParam> products;
private double subtotal;
@Override
public void myCartContainsTheFollowingProducts(List<ProductsParam> products) {
this.products = products;
}
@Override
public void iCalculateTheSubtotal() {
subtotal = products.stream()
.mapToDouble(product -> product.qty() * product.unitPrice())
.sum();
}
@Override
public void theCartSubtotalShouldBe$p1(Double expectedSubtotal) {
assertEquals(expectedSubtotal, subtotal, 0.001);
}
// … the stock-check and bulk-discount steps work the same way
}
ShoppingCartFeature.java (marker class, bare @Gherkin2JUnit)
└→ ShoppingCartScenarios.java (generated, abstract — the Param classes and @Test methods)
└→ ShoppingCartTest.java (your concrete class, implements the steps)
Key points
- Each unique data table shape in a feature produces its own
Paramclass - Field accessors use record-style names (
name(), notgetName()) - No Cucumber dependency — these are plain Java classes
- For compile-time enum safety on a column, see Type Refinement with Enums
Run it
cd examples/common-use-cases/example-1
mvn test