Step Definitions & Expressions

Cucumber Expressions, regex, parameter types

How Step Matching Works

When Cucumber runs a scenario, it reads each Gherkin step and searches through all your Java classes (in the glue package) for a method whose annotation matches the step text. Here’s the flow:

Gherkin Step:  "When I add 5 and 3"
                        ↓
Cucumber scans all classes in the glue package
                        ↓
Finds:  @When("I add {int} and {int}")
        public void iAddAnd(int a, int b)
                        ↓
Extracts: a = 5, b = 3
                        ↓
Executes the method with those values

Cucumber Expressions

Cucumber 7+ introduced Cucumber Expressions as a simpler alternative to regular expressions. They’re the recommended approach:

Built-in Parameter Types

ExpressionMatchesJava TypeExample Gherkin
{int}Integersint or IntegerI have 5 items
{float}Decimal numbersfloat or FloatThe price is 9.99
{string}Text in quotesStringI search for “laptop”
{word}Single word (no spaces)StringI am on the login page
{}Any text (anonymous)StringMatches anything
{bigdecimal}Precise decimalsBigDecimalThe total is 1234.56
{double}Double-precision floatdoubleThe rate is 3.14
{byte}Byte valuebyteStatus code is 1
{short}Short integershortPort 8080
{long}Long integerlongID is 123456789

Examples in Action

// {string} — captures text in double or single quotes
@When("I search for {string}")
public void iSearchFor(String query) {
    searchPage.search(query);
}
// Matches: When I search for "laptop"
// Matches: When I search for 'laptop'

// {int} — captures whole numbers
@Then("I should see {int} results")
public void iShouldSeeResults(int count) {
    Assert.assertEquals(searchPage.getResultCount(), count);
}
// Matches: Then I should see 10 results

// {word} — captures a single word (no quotes needed)
@Given("I am on the {word} page")
public void iAmOnPage(String pageName) {
    navigator.goTo(pageName);
}
// Matches: Given I am on the login page
// Matches: Given I am on the dashboard page

// {float} — captures decimal numbers
@Then("the total should be {float}")
public void theTotalShouldBe(float total) {
    Assert.assertEquals(cartPage.getTotal(), total, 0.01);
}
// Matches: Then the total should be 29.99

Optional Text & Alternation

Optional Text — Parentheses

Wrap optional words in parentheses. This lets one step definition match multiple variations:

// Matches both "I click login button" and "I click the login button"
@When("I click (the )login button")
public void iClickLoginButton() {
    loginPage.clickLogin();
}

// Matches "I have 1 item" and "I have 5 items"
@Then("I have {int} item(s)")
public void iHaveItems(int count) {
    Assert.assertEquals(cart.getItemCount(), count);
}

// Matches "file is present" and "file is not present"
@Then("the file is (not )present")
public void theFileIsPresent() {
    // handle both cases
}

Alternation — Forward Slash

Use / to offer word choices:

// Matches "I add the item" and "I remove the item"
@When("I add/remove the item")
public void iModifyItem() {
    // handle both actions
}

// Matches "color is red", "color is blue", "color is green"
@Then("color is red/blue/green")
public void colorIs() { }

// Combined with parameter capture
@When("I click the accept/reject button")
public void iClickButton() { }

Regular Expressions

Before Cucumber Expressions existed, regex was the only option. You can still use regex when you need more power. Cucumber detects regex by the presence of ^ and $ anchors:

// Regex — anchored with ^ and $
@When("^I enter \"([^\"]*)\" in the (.*) field$")
public void iEnterInField(String value, String fieldName) {
    page.enterValue(fieldName, value);
}
// Matches: When I enter "john" in the username field
// Matches: When I enter "test@email.com" in the email field

// Regex with numeric capture
@Then("^I should see (\\d+) results?$")
public void iShouldSeeResults(int count) {
    Assert.assertEquals(searchPage.getResultCount(), count);
}
// Matches: Then I should see 1 result
// Matches: Then I should see 10 results

Cucumber Expressions vs Regex — Comparison

FeatureCucumber ExpressionRegex
ReadabilityEasy to readHarder to read
Type safetyBuilt-in ({int}, {string})Manual casting
Custom types@ParameterTypeCapture groups
Optional text(optional)(?:optional)?
Alternationa/b/c`(?:a
DetectionNo ^ or $Starts with ^, ends with $
RecommendationPreferred for new projectsUse when complex patterns are needed

Custom Parameter Types

When the built-in types ({int}, {string}, etc.) aren’t enough, create your own with @ParameterType:

Boolean from Text

@ParameterType("enabled|disabled")
public Boolean status(String value) {
    return "enabled".equals(value);
}

// Usage
@Then("the feature should be {status}")
public void featureShouldBe(Boolean isEnabled) {
    Assert.assertEquals(isEnabled, feature.isEnabled());
}
// Matches: Then the feature should be enabled
// Matches: Then the feature should be disabled

Enum Mapping

public enum UserRole { ADMIN, USER, VIEWER }

@ParameterType("admin|user|viewer")
public UserRole role(String value) {
    return UserRole.valueOf(value.toUpperCase());
}

// Usage
@Given("I am logged in as a {role}")
public void loggedInAs(UserRole role) {
    loginHelper.loginAs(role);
}
// Matches: Given I am logged in as a admin
// Matches: Given I am logged in as a viewer

Object from Pattern

// Parse dates from Gherkin text
@ParameterType("\\d{4}-\\d{2}-\\d{2}")
public LocalDate date(String value) {
    return LocalDate.parse(value);
}

// Usage
@Given("the report date is {date}")
public void reportDate(LocalDate date) {
    reportPage.setDate(date);
}
// Matches: Given the report date is 2026-02-09

Composite Custom Type

// Parse money amounts
@ParameterType("\\$[\\d,]+\\.\\d{2}")
public BigDecimal amount(String value) {
    return new BigDecimal(value.replace("$", "").replace(",", ""));
}

// Usage
@Then("the total should be {amount}")
public void totalShouldBe(BigDecimal expected) {
    Assert.assertEquals(expected, cart.getTotal());
}
// Matches: Then the total should be $1,299.99

Step Definition Patterns

Pattern 1: One Step Definition Class per Feature

stepdefinitions/
├── LoginSteps.java          // Steps for login.feature
├── CartSteps.java           // Steps for cart.feature
├── CheckoutSteps.java       // Steps for checkout.feature
└── CommonSteps.java         // Shared steps used across features

Pattern 2: Steps Organized by Layer

stepdefinitions/
├── ui/
│   ├── LoginUISteps.java
│   └── CartUISteps.java
├── api/
│   ├── UserAPISteps.java
│   └── OrderAPISteps.java
└── common/
    ├── NavigationSteps.java
    └── AssertionSteps.java

Reusable Steps

Since step definitions are global, a step defined once can be used across all feature files:

// CommonSteps.java — Reusable across all features
public class CommonSteps {

    @Given("I am on the {word} page")
    public void iAmOnPage(String pageName) {
        navigator.navigateTo(pageName);
    }

    @Then("I should see a success message")
    public void iShouldSeeSuccessMessage() {
        Assert.assertTrue(page.isSuccessMessageDisplayed());
    }

    @Then("I should see an error {string}")
    public void iShouldSeeError(String errorMessage) {
        Assert.assertEquals(errorMessage, page.getErrorMessage());
    }

    @When("I wait for {int} seconds")
    public void iWaitForSeconds(int seconds) {
        Thread.sleep(seconds * 1000L);
    }
}
# login.feature — uses CommonSteps
Scenario: Failed login
  Given I am on the login page         # CommonSteps
  When I login with invalid password    # LoginSteps
  Then I should see an error "Invalid"  # CommonSteps

# registration.feature — same CommonSteps
Scenario: Successful registration
  Given I am on the register page       # CommonSteps
  When I submit the registration form   # RegistrationSteps
  Then I should see a success message   # CommonSteps

Avoiding Ambiguous Steps

The most common error in Cucumber is the ambiguous step error. It occurs when a Gherkin step matches more than one step definition:

io.cucumber.core.exception.CucumberException:
  Step "I click the button" matches more than one step definition:
    iClickButton() in LoginSteps
    iClickTheButton() in CommonSteps

Common Causes and Fixes

CauseExampleFix
Duplicate definitionsTwo methods with @When("I click the button")Delete one, keep in a shared class
Overlapping patterns"I am on the {word} page" and "I am on the login page"Remove the specific one — the generic one handles it
Regex too broad"^I (.*)$" matches everythingMake the regex more specific
Copy-paste leftoversSame step in old and new step classSearch your glue package for duplicates

Prevention Strategies

Step Definition Best Practices

Thin Step Definition Example

// GOOD — Step definition delegates to Page Object
@When("I login with {string} and {string}")
public void iLoginWith(String username, String password) {
    loginPage.login(username, password);
}

// BAD — Step definition contains all the logic
@When("I login with {string} and {string}")
public void iLoginWith(String username, String password) {
    driver.findElement(By.id("username")).clear();
    driver.findElement(By.id("username")).sendKeys(username);
    driver.findElement(By.id("password")).clear();
    driver.findElement(By.id("password")).sendKeys(password);
    driver.findElement(By.id("login-btn")).click();
    new WebDriverWait(driver, Duration.ofSeconds(10))
        .until(ExpectedConditions.urlContains("/dashboard"));
}

Handling Undefined Steps

When Cucumber encounters a step with no matching definition, it reports it as undefined and suggests a snippet:

Step undefined
You can implement it using the snippet(s) below:

@Given("I have {int} items in stock")
public void i_have_items_in_stock(Integer int1) {
    // Write code here that turns the phrase above into concrete actions
    throw new io.cucumber.java.PendingException();
}

Workflow for New Steps

  1. Write the Gherkin scenario in the feature file
  2. Run with dryRun = true
  3. Copy the generated snippets
  4. Paste into the appropriate step definition class
  5. Replace PendingException with real implementation
  6. Switch to dryRun = false and run

Summary