Implementing Cucumber Expressions for Step Definitions in Java
Replace regex with Cucumber expressions in Java step definitions. Covers built-in parameter types, custom type registration, optional segments, dry-run validation, and resolving ambiguous matches.
04 Aug 2025, 07:42 UTC

Desired Outcome
Replace regular expressions in Cucumber step definitions with human‑readable Cucumber expressions, using built‑in and custom parameter types to keep step definitions concise and maintainable.
Prerequisites
- Java 11 or later
- Maven or Gradle build tool
- cucumber-java dependency (version 7.x or 8.x) on the test classpath
- A Cucumber runner (JUnit Platform or TestNG) configured to execute feature files
Add the dependency to your build file:
<!-- Maven -->
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>8.22.0</version>
<scope>test</scope>
</dependency>// Gradle (Kotlin DSL)
testImplementation("io.cucumber:cucumber-java:8.22.0")Procedure
1. Write a Feature File Using Expression Syntax
Create src/test/resources/features/shopping.feature:
Feature: Shopping cart
Scenario: Add items to cart
Given I have {int} apples in my cart
And the cart total is {float} dollars
And the cart is {bool} empty
When I add {string} to the cart
Then the cart contains {int} itemsCurly braces denote parameters. The words int, float, bool, and string are built‑in parameter types.
2. Implement Matching Step Definitions
Create src/test/java/steps/ShoppingSteps.java:
package steps;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.When;
import io.cucumber.java.en.Then;
import static org.junit.jupiter.api.Assertions.*;
public class ShoppingSteps {
private int appleCount = 0;
private double total = 0.0;
private boolean empty = true;
private int itemCount = 0;
@Given("I have {int} apples in my cart")
public void iHaveApples(int count) {
this.appleCount = count;
this.empty = false;
}
@Given("the cart total is {float} dollars")
public void theCartTotalIs(double total) {
this.total = total;
}
@Given("the cart is {bool} empty")
public void theCartIsEmpty(boolean empty) {
this.empty = empty;
}
@When("I add {string} to the cart")
public void iAddToCart(String item) {
itemCount++;
if ("apple".equalsIgnoreCase(item)) appleCount++;
}
@Then("the cart contains {int} items")
public void theCartContainsItems(int expected) {
assertEquals(expected, itemCount);
}
}Each parameter in the expression maps to a method argument of the corresponding Java type.
3. Register Custom Parameter Types (Optional)
For domain‑specific values, register a custom type in a configuration class:
package config;
import io.cucumber.java.Before;
import io.cucumber.java.ParameterType;
import java.util.Arrays;
import java.util.List;
public class CucumberConfig {
@ParameterType(name = "fruit", regexp = "apple|banana|orange")
public Fruit fruit(String name) {
return Fruit.valueOf(name.toUpperCase());
}
public enum Fruit { APPLE, BANANA, ORANGE }
// Register the type with Cucumber's TypeRegistry
@Before
public void registerTypes(io.cucumber.java.TypeRegistry registry) {
registry.defineParameterType(new io.cucumber.java.ParameterType<>(
"fruit",
"apple|banana|orange",
Fruit.class,
this::fruit
));
}
}Now you can write steps like Given I pick a {fruit} and receive a Fruit enum directly.
4. Use Optional Segments and Wildcards
Expressions support optional text with parentheses and wildcards with *:
@Given("the user {string} (?:is )?logged in")
public void userLoggedIn(String username) { ... }
@When("I search for *")
public void iSearchFor(String query) { ... }The first matches both "the user alice is logged in" and "the user bob logged in". The second captures everything after "I search for ".
Expected Checks
Run the Test Suite
Execute with Maven or Gradle:
mvn test
# or
./gradlew testAll scenarios should pass. The console output shows each step matched and executed.
Dry‑Run Validation
Verify expressions parse correctly without executing step code:
mvn exec:java -Dexec.mainClass=io.cucumber.core.cli.Main \
-Dexec.args="--dry-run --glue steps src/test/resources/features"
# Gradle equivalent:
./gradlew cucumberDryRunLook for 0 scenarios and no Undefined step or Ambiguous step messages.
Check for Ambiguity
If Cucumber reports Ambiguous step definitions, two expressions match the same step. Make expressions more specific or reorder step definitions (first match wins).
Recovery Options
Syntax Errors in Expressions
If a step fails with cucumber.expressions.ExpressionException, the expression syntax is invalid. Common causes:
- Unclosed braces:
{intinstead of{int} - Unknown parameter type:
{uuid}without registering auuidtype - Invalid regex in custom
@ParameterType
Fix the expression, recompile, and re‑run the dry‑run.
Ambiguous Matches
When multiple step definitions match, Cucumber throws cucumber.runtime.AmbiguousStepDefinitionsException. Resolve by:
- Adding more literal text to one expression
- Using a custom parameter type with a restrictive regex
- Removing the less‑specific definition
Missing Parameter Type Registration
If a custom type isn't recognized, ensure the @Before method runs before scenarios (place it in a class annotated with @CucumberContextConfiguration or a shared hooks class). Verify with --dry-run.
Limitations
- Cucumber expressions are not a full regex replacement; complex validation still requires custom parameter types with regex.
- Expression parsing occurs at runtime; syntax errors surface only when the feature file is loaded.
- Overly generic expressions (e.g.,
{string}everywhere) reduce readability and increase ambiguity risk.
Verification Checklist
- Feature file steps use
{type}placeholders matching built‑in or registered types. - Step definition methods have parameters in the same order and compatible Java types.
mvn test(or Gradle equivalent) passes with zero failures.--dry-runreports no undefined or ambiguous steps.- Custom parameter types are registered before scenario execution (via
@BeforeorTypeRegistryConfigurer).
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.