Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A second Cucumber feature normally uses the same step-definition registry as the first. When its steps are reported as undefined, the feature filename is rarely the cause. Check, in order, that Cucumber or Behave discovers the implementation, that the complete step text matches one expression, that captured arguments have the right shape, and that no duplicate definition creates ambiguity. The same checks distinguish an undefined step from an arity mismatch or a real failure inside your code.

What “undefined” means

Cucumber loads step definitions before it executes feature text. Given, When, and Then are keywords for readable scenarios; they do not create separate matching namespaces. The text after the keyword must match one registered Cucumber expression or regular expression. A feature file does not own a private set of definitions, so adding checkout.feature does not require a new step-definition file.

Message or state What it means First place to look
Undefined No loaded definition matches the step text, or the definition was not discovered. Glue package, steps directory, and exact wording.
Ambiguous or duplicate Two or more loaded definitions match the same step. Overlapping expressions or duplicate files.
Arity mismatch The definition was selected, but the number of captured and supplied arguments differs. Expression groups, method parameters, data tables, and doc strings.
Failed The implementation ran and raised an assertion, exception, or other error. The implementation and its test data, not discovery.

These distinctions follow the load-and-match behavior documented by Cucumber and its FAQ.

1. Verify discovery configuration first

Cucumber-JVM: inspect the glue package

Without an explicit setting, Cucumber-JVM searches from the package containing the runner class and its subpackages. If the second feature is executed by a runner whose package is different from the package containing your definitions, the methods can exist and still be invisible. Set glue to the package that contains the definition classes (and any hook classes).

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
@RunWith(Cucumber.class)
@CucumberOptions(
    features = "src/test/resources/features",
    glue = "com.example.bdd.steps",
    plugin = {"pretty"}
)
public class RunCucumberTest {
}

For a JUnit 5 suite, the equivalent configuration is commonly placed on @ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "com.example.bdd.steps") (with the Cucumber JUnit Platform engine). In a command-line setup, use the same glue package each time, for example:

mvn test -Dcucumber.glue=com.example.bdd.steps -Dcucumber.features=src/test/resources/features/orders.feature

Show the three locations together when debugging: the feature path, the Java package/file containing the step, and the runner’s glue value. A common mistake is pointing glue at com.example.bdd in one runner and at an unrelated test package in another.

Behave: check the feature tree and steps directory

Behave imports Python files from a steps directory associated with the feature before running scenarios. A conventional layout is:

project/
├── features/
│   ├── login.feature
│   ├── checkout.feature
│   └── steps/
│       ├── authentication_steps.py
│       └── checkout_steps.py

Run Behave from the directory that contains features, or pass the intended feature path explicitly. If checkout.feature was copied outside that tree, its step modules may not be imported. Confirm that the implementation file is a Python module under the expected steps directory and that importing it does not fail because of a syntax or dependency error. Behave’s loading and directory rules are described in its feature setup documentation and API reference.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
from behave import given, when, then

@given('a registered customer')
def step_registered_customer(context):
    context.customer = create_customer()

If the file is present but its module import raises an exception, Behave never registers the decorators. Fix that import error before interpreting later steps as undefined.

2. Compare the complete step text

Matching starts after Given, When, or Then. Compare the second feature character by character with the registered expression: wording, punctuation, numbers, quoted values, and pluralization all matter. “the user logs in” and “the user signs in” are different text unless one expression deliberately covers both.

Before: wording that does not match

// Definition
@When("the account owner signs in")
public void accountOwnerSignsIn() { ... }

# checkout.feature
When the account owner logs in

After: reuse the existing expression

# checkout.feature
When the account owner signs in

Alternatively, change the expression intentionally and update every caller:

@When("the account owner {word}s in")
public void accountOwnerSignsIn(String verb) { ... }

Do not broaden an expression merely to hide inconsistent business language. Prefer one clear phrase for a business action and reuse it across features. The Cucumber API documentation explains expression parameters and captured values; Behave decorators similarly match the feature-step string.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

3. Check captured arguments, tables, and doc strings

A step can look identical while its argument shape has changed. Each capture in a Cucumber expression or regex must correspond to a method parameter, in order. A scenario data table or doc string is an additional argument after the captured text.

Add the parameter or remove it consistently

# Feature
When I transfer 250 USD to the savings account

// Matching definition
@When("I transfer {int} {word} to the savings account")
public void transfer(int amount, String currency) {
    ...
}

If the method still accepts only int amount, Cucumber reports an arity problem rather than executing successfully. With a regular expression, count capture groups—including optional groups—and compare that count with the method signature. The FAQ calls this out as a separate failure class.

Account for a data table or doc string

When I create this account:
  | type    | currency |
  | savings | USD      |
@When("I create this account:")
public void createAccount(DataTable table) {
    ...
}

In Behave, the table is available as context.table and a doc string as context.text; the decorator still has to match the text before that attached data. Do not encode table rows into the expression itself.

4. Remove duplicate and overlapping definitions

All discovered definitions are loaded before execution. If both authentication_steps.java and checkout_steps.java match “the customer is logged in,” Cucumber cannot choose reliably and reports an ambiguous or duplicate definition. Search the entire configured glue scope, not only the directory you edited.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Keep one implementation for a shared business action.
  • Narrow expressions that accidentally match one another.
  • Delete copied definitions left behind after moving a step file.
  • Run with the same glue configuration used in continuous integration; a broader local package can reveal duplicates that CI does not load.

The guidance on reusable organization and the anti-pattern of feature-coupled definitions is covered by step organization and Cucumber’s anti-patterns guide.

5. Organize steps for reuse, not by feature filename

Group definitions by capability—authentication, orders, payments, or notifications—rather than creating a private file for every .feature file. One feature may use several capability files, and one capability file may serve many features. This keeps setup and actions reusable and avoids two implementations drifting apart.

Creating a new file is reasonable when a capability is genuinely new or a module boundary improves maintenance. It is not a fix for an undefined step by itself: the new file still must be under the discovered glue or steps path, and its expression still must match the text.

6. Run a focused verification, then the full suite

  1. Run only the second feature with the same runner, glue package, and environment used for the first.
  2. Confirm the status changes from undefined to passed, failed, or ambiguous. Each result points to a different next step.
  3. If it passes, run both features together. Shared state, hooks, duplicate expressions, or order-dependent setup can appear only in the combined run.
  4. Run the complete suite in CI-equivalent configuration and review the first error, not only the final summary.

A focused run is a diagnostic procedure, not proof that the whole suite is healthy. The full run is where cross-feature state and duplicate discovery are exposed.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Common symptoms and precise fixes

Symptom Likely cause Fix
Every step in the second file is undefined Wrong Cucumber-JVM glue package, feature outside Behave’s tree, or step module import failure. Print or inspect the runner path, move the feature/module, and fix import errors.
Only one newly worded step is undefined Text differs from the expression. Make wording identical or add a deliberate parameterized expression.
Error mentions argument count Capture groups, table/doc-string arguments, and method parameters disagree. Align the expression and method signature.
Ambiguous step Two definitions overlap. Delete the duplicate or narrow one expression.
Step is reported failed The definition was found and ran, but application/test code raised an error. Debug the implementation, fixture, assertion, or external dependency.
Works locally, undefined in CI Different working directory, test source set, package, or glue/steps argument. Make CI’s command and classpath explicit and reproduce it locally.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If you need a clean visual record of a Cucumber HTML report or failure page while diagnosing a pipeline, ScreenshotNeo can capture the URL through one HTTP request. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

See the ScreenshotNeo documentation for parameters and authentication.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://cucumber.io/docs/faq/ -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://cucumber.io/docs/faq/"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://cucumber.io/docs/faq/' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots; every feature is on every plan, and yearly billing provides two months free. Create a free ScreenshotNeo account to try it without a card.

FAQ

Do Given, When, and Then require separate implementations?

No. The keyword is not a matching namespace. The text and its arguments determine which unique definition is selected.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Can one step-definition method be used by multiple scenarios?

Yes. Reuse is the normal model; keep the expression stable and make the method’s inputs explicit.

Should I split definitions when the suite becomes large?

Split by business capability or technical responsibility, while keeping all relevant files inside the configured discovery scope.

Why does changing only a data table expose a new error?

The step text may still match, but the attached table changes the arguments supplied to the implementation. Update table mapping and validation rather than creating a second copy of the step.

Frequently Asked Questions

Do Given, When, and Then require separate implementations?

No. The keyword is not a matching namespace; the step text and its arguments select the definition.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Can one step-definition method serve multiple scenarios?

Yes. Reuse is expected when the business action and expression are the same.

Should a large suite have one step file?

No requirement exists for one file. Split by capability while keeping every file under the configured discovery path.

Why can changing a data table reveal a new error?

The text can still match while the attached table changes the arguments supplied to the implementation.

The Bottom Line

A second feature file normally needs no new step-definition file. Fix discovery first, then exact expression text, argument arity, and duplicate matches; once those are correct, a remaining failure is in the implementation or its test data.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.