Cucumber Scenario Outline: Data-Driven Testing in Java with Parameter Injection
Use Cucumber Scenario Outline to run one Gherkin template against many data rows. This guide shows the parameter-binding mechanism, a complete discount-tier example, memory limits, and common binding mistakes.
01 Jul 2026, 15:56 UTC

The Problem: Repeating Test Logic Across Multiple Data Sets
When testing business rules that apply to many input combinations—discount tiers, validation boundaries, permission matrices—writing a separate Scenario for each case bloats feature files and makes maintenance painful. Cucumber's Scenario Outline solves this by letting you define a single Gherkin template with placeholders, then feed it rows of concrete data from an Examples table. At runtime, Cucumber expands each row into a distinct scenario, injecting values into step definitions via parameter binding.
How Scenario Outline Works
A Scenario Outline looks like a regular Scenario, but step text contains placeholders written as <columnName>. An Examples table below the outline provides columns that match those placeholders. During execution, Cucumber's runner (via cucumber-java) reads the table, computes the Cartesian product of all rows, and generates one scenario instance per row. Each instance runs the same step definitions with different arguments.
Worked Example: Discount Tier Validation
Suppose an e-commerce service applies discounts based on order total and customer tier. Instead of six separate scenarios, one outline covers all combinations.
Feature: Volume discount calculation
Scenario Outline: Apply tiered discount for eligible orders
Given a customer with tier "<tier>"
And an order total of <orderTotal>
When the discount engine runs
Then the applied discount percent should be <expectedDiscount>
Examples:
| tier | orderTotal | expectedDiscount |
| BRONZE | 50 | 0 |
| BRONZE | 150 | 5 |
| SILVER | 50 | 2 |
| SILVER | 150 | 10 |
| GOLD | 50 | 5 |
| GOLD | 150 | 15 |
Step Definition Binding with Cucumber Expressions
Step definitions use Cucumber Expressions (not regex) to capture placeholders. The parameter type is inferred from the method signature. Placeholders in the Gherkin step (<tier>) map to method parameters by name when you use the @ParameterType annotation or rely on default type mapping.
package com.example.steps;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class DiscountSteps {
private String customerTier;
private int orderTotal;
private int actualDiscount;
@Given("a customer with tier {string}")
public void setCustomerTier(String tier) {
this.customerTier = tier;
}
@Given("an order total of {int}")
public void setOrderTotal(int total) {
this.orderTotal = total;
}
@When("the discount engine runs")
public void runDiscountEngine() {
// Production code call
DiscountEngine engine = new DiscountEngine();
this.actualDiscount = engine.calculate(customerTier, orderTotal);
}
@Then("the applied discount percent should be {int}")
public void verifyDiscount(int expected) {
assertEquals(expected, actualDiscount,
"Discount mismatch for tier " + customerTier + " at total " + orderTotal);
}
}
No @Parameterized annotation is required on the step method itself—Cucumber matches the {string} and {int} expression tokens to the method parameters automatically. The Examples table columns (tier, orderTotal, expectedDiscount) must match the placeholder names exactly (case-sensitive).
Running the Outline
With Maven, the cucumber-java and cucumber-junit-platform-engine dependencies (version 7.x or 8.x) plus the maven-surefire-plugin or maven-failsafe-plugin execute the generated scenarios. A minimal pom.xml snippet:
<dependencies>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>8.22.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit-platform-engine</artifactId>
<version>8.22.0</version>
<scope>test</scope>
</dependency>
</dependencies>
Run with mvn test. The console output shows each expanded scenario, e.g.:
Scenario Outline: Apply tiered discount for eligible orders # discount.feature:3
Examples:
| tier | orderTotal | expectedDiscount |
| BRONZE | 50 | 0 | # Passed
| BRONZE | 150 | 5 | # Passed
...
Limits and Performance Considerations
Combinatorial Explosion
If you add multiple Examples tables to the same Scenario Outline, Cucumber computes the Cartesian product across all tables. Two tables of 50 rows each produce 2,500 scenarios. The research notes a practical ceiling around 1,000 examples per outline before default JVM heap (typically 256–512 MB) becomes a bottleneck. Symptoms: OutOfMemoryError, slow test startup, or IDE indexer hangs.
Mitigation Strategies
- Split outlines by logical domain (e.g., separate outlines for "new customer" vs "returning customer" discounts).
- External data sources: Use a
ParameterTypethat reads CSV/JSON at runtime, keeping the feature file small. Example:
Then the outline references@ParameterType(name = "discountRow", regexp = ".+") public DiscountRow readDiscountRow(String line) { return CsvParser.parse(line, DiscountRow.class); }<discountRow>and a single-columnExamplestable lists file paths or inline CSV strings. - Tag filtering: Annotate outlines with
@smokeor@regressionand run subsets viacucumber.filter.tags.
Common Mistakes
| Mistake | Symptom | Fix |
|---|---|---|
Placeholder name mismatch (e.g., <Tier> vs column tier) |
Cucumber reports Undefined step or binds null |
Match case exactly; use lower-case in both places |
Type mismatch: column holds "150.00" but step expects {int} |
Binding fails silently; parameter receives default (0) or throws CucumberExpressionException |
Use {double} or {bigdecimal} in step definition; keep column format consistent |
| Unicode characters in .feature file saved as ISO-8859-1 | Parsing error: "Invalid byte sequence" or garbled placeholders | Save feature files as UTF-8; configure IDE/editor encoding; add -Dfile.encoding=UTF-8 to Maven surefire |
| Duplicate column names in Examples table | Only last column bound; earlier values lost | Ensure unique column headers |
Missing Examples keyword (typo: Example:) |
Outline runs zero scenarios; no error | Keyword must be plural Examples: |
Verification Checklist
- Create the
.featurefile with Scenario Outline and Examples block. - Run
mvn test -Dcucumber.filter.name="Apply tiered discount"to isolate the outline. - Confirm each row appears as a separate scenario in the test report (JUnit XML or Cucumber HTML report).
- For large tables, add
-Xmx1gtoargLinein surefire config and monitor heap withjcmd <pid> GC.heap_infoduring execution. - Validate step definition signatures match placeholder types:
{string}→String,{int}→int/Integer,{float}→double/Float,{bigdecimal}→BigDecimal.
When Not to Use Scenario Outline
- Scenarios require fundamentally different setup/teardown steps—use separate
Scenarioblocks orBackgroundwith conditional logic. - Data sets exceed ~500 rows and change frequently—external data-driven approach (CSV, database) keeps feature files readable.
- You need cross-browser or cross-environment matrix testing—delegate to test infrastructure (Testcontainers, Selenium Grid) rather than Gherkin combinatorics.
Summary
Scenario Outline turns repetitive Gherkin into a maintainable template. The mechanism is straightforward: placeholders in steps, columns in Examples, automatic parameter binding via Cucumber Expressions. Keep tables under a few hundred rows, watch for type/encoding mismatches, and split or externalize data when the Cartesian product threatens build time or memory. The worked discount example above compiles and runs on Cucumber 8.x with Java 17+; adjust versions as needed for your stack.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.