Undefined Step Generation in Cucumber JVM
1. Introduction to Undefined Step Generation
Undefined Step Generation is one of the most useful productivity features in Cucumber JVM. It appears when Cucumber reads a Gherkin step from a feature file but cannot find any matching Java step definition method in the configured glue packages. Instead of only saying that the step failed, Cucumber helps the team by printing a ready-made step definition template in the console. The developer can copy that template into the correct step definition class and then replace the placeholder code with real automation logic.
This feature is especially helpful when a team writes scenarios before writing automation. In Behavior Driven Development, feature files often come first. Product owners, testers, developers, and automation engineers agree on behavior using business-readable examples. At that stage, many steps may not yet have Java code behind them. Undefined Step Generation makes the gap visible. It shows exactly which Gherkin phrases still need implementation and gives the developer the correct annotation syntax to start with.
Understanding undefined steps is important because many Cucumber execution problems are not application defects. They are mapping problems. A scenario may fail because the step definition does not exist, because the annotation text is slightly different, because the glue package is wrong, or because the method is sitting in a package Cucumber never searches. When you understand how Undefined Step Generation works, you can quickly separate missing automation from real product failures.
2. What Is an Undefined Step?
No Matching Java Method Found
An undefined step is a Gherkin step that Cucumber cannot map to any Java method. The step exists in the feature file, but no matching step definition exists in the configured glue packages. Cucumber can read the step as text, but it does not know what code should execute for that text.
Consider this feature file:
Scenario: Successful login
Given the user has valid credentials
When the user logs in
Then the dashboard should be displayed
If there is no step definition for the phrase "the user has valid credentials", Cucumber marks that step as undefined. It does not matter that the English sentence is clear to a human reader. Cucumber needs a Java method with an annotation pattern that matches the step text.
An undefined step is not the same as a failed assertion. A failed assertion means the code ran and the expected result did not match the actual result. An undefined step means Cucumber could not even find code to run. That distinction is essential for debugging test results correctly.
3. What Is Undefined Step Generation?
Undefined Step Generation is Cucumber's automatic suggestion mechanism for missing step definitions. When Cucumber encounters an undefined step, it generates a Java method skeleton that can be used as the starting point for implementation. The generated snippet includes the correct Cucumber annotation, a method name derived from the step text, and usually a PendingException to indicate that the method still needs real code.
For example, if this step is undefined:
Given the user has valid credentials
Cucumber may generate a snippet similar to this:
@Given("the user has valid credentials")
public void the_user_has_valid_credentials() {
// Write code here that turns the phrase above into concrete actions
throw new io.cucumber.java.PendingException();
}
This generated method is not the final implementation. It is a template. The developer copies it into the appropriate step definition class, gives the method a meaningful name if needed, and replaces the PendingException with real automation code. The generated annotation is usually the most valuable part because it matches the feature text exactly and avoids typing mistakes.
4. Why Undefined Step Generation Exists
Cucumber exists to connect human-readable examples with executable automation. In a growing BDD framework, feature files and step definitions evolve together. Undefined Step Generation reduces friction during that evolution. It helps developers create missing mappings quickly and consistently instead of manually writing every annotation from scratch.
The feature also supports a natural BDD workflow. Teams can discuss and write scenarios first, run them, see which steps are undefined, and then implement only the missing steps. This encourages business-first scenario design. The team does not need to begin with Java code or Selenium actions. They can begin with behavior and let Cucumber show the implementation gaps.
Undefined Step Generation also prevents small syntax mistakes. Annotation text must match step text closely. A missing article, plural difference, spelling issue, or punctuation change can prevent mapping. When Cucumber generates the snippet, it uses the text from the feature file, so the initial mapping is correct. The developer can then focus on the automation logic instead of annotation syntax.
5. How Undefined Step Generation Works
Execution Flow
During execution, Cucumber reads the runner configuration, loads feature files, scans glue packages, and registers all available step definition patterns. Then it starts executing scenarios step by step. For each step, it searches the registered step definitions for a match. If a match is found, the Java method is executed. If no match is found, Cucumber reports the step as undefined and prints a suggested method template.
Runner starts
|
v
Read feature file
|
v
Read Gherkin step
|
v
Search glue packages
|
v
Matching step definition found?
|
+-- Yes -> Execute Java method
|
+-- No -> Generate step definition template
Mark step as undefined
This means Undefined Step Generation depends on the same discovery and mapping rules as normal execution. If a step definition exists but is outside the configured glue package, Cucumber still treats the step as undefined. If the annotation text is slightly wrong, the step remains undefined. If the method is in the correct package but the project is not rebuilt, the runtime may still not see it.
6. Example of Undefined Step Generation
Suppose a team writes a new login scenario:
Feature: Login
Scenario: Login with valid credentials
Given the user has valid credentials
When the user logs in
Then the dashboard should be displayed
At this point, assume no step definitions exist. When the scenario runs, Cucumber cannot map any of the three steps. It prints snippets for each missing step:
@Given("the user has valid credentials")
public void the_user_has_valid_credentials() {
throw new io.cucumber.java.PendingException();
}
@When("the user logs in")
public void the_user_logs_in() {
throw new io.cucumber.java.PendingException();
}
@Then("the dashboard should be displayed")
public void the_dashboard_should_be_displayed() {
throw new io.cucumber.java.PendingException();
}
The developer should copy these methods into a suitable class such as LoginSteps.java. After that, the methods should be implemented with real logic or delegation. The Given step might prepare test data, the When step might call a login page object or API helper, and the Then step might verify that the dashboard is visible or that the login session is active.
7. Understanding the Generated Template
The generated template usually contains three important parts: the annotation, the Java method, and the PendingException. The annotation is the mapping pattern. It tells Cucumber which Gherkin step this method handles. The Java method is where implementation logic will go. The PendingException is a temporary marker that says the method exists but has not been implemented yet.
@Given("the user has valid credentials")
public void the_user_has_valid_credentials() {
// Write code here that turns the phrase above into concrete actions
throw new io.cucumber.java.PendingException();
}
The generated method name is based on the step text. It is often written with underscores, especially in snippets. Teams can keep it or rename it to match their Java style. The method name does not control mapping. The annotation does. However, meaningful method names are still recommended because they make the step definition class easier to read and debug.
The generated comment is only a reminder. It should not remain as the final implementation. The final method should delegate to page objects, API clients, service classes, or assertion helpers depending on the layer being tested.
8. Why PendingException Is Included
Recognized but Not Implemented
Cucumber includes PendingException in generated snippets to make unfinished implementation explicit. Once the generated method is copied into a step definition class, the step is no longer undefined because Cucumber can now map it. However, the method still does not perform real work. Throwing PendingException tells the report that the step is recognized but pending implementation.
Before implementation, the method may look like this:
@Given("the user has valid credentials")
public void userHasValidCredentials() {
throw new PendingException();
}
After implementation, it should look more like this:
@Given("the user has valid credentials")
public void userHasValidCredentials() {
loginTestData.prepareValidUser();
}
The PendingException must be removed when the step is complete. Leaving PendingException in committed automation code is a common mistake. It creates the impression that the step exists, but it guarantees the scenario cannot pass. In a mature framework, pending steps should be tracked intentionally and cleaned up quickly.
9. Where the Generated Template Appears
The generated snippets usually appear in the test execution output. If tests are run from an IDE, they appear in the IDE run console. If tests are run through Maven or Gradle, they appear in the build output. If tests run in Jenkins, GitHub Actions, Azure DevOps, or another CI tool, the snippets appear in the job logs.
You can implement missing steps with the snippets below:
@Given("the user has valid credentials")
public void the_user_has_valid_credentials() {
throw new io.cucumber.java.PendingException();
}
In local development, this output is easy to copy. In CI, the output is more useful as a diagnostic signal. A CI job should not normally be the first place where new undefined steps are discovered. Developers should run scenarios locally or as part of branch checks before merging. Still, CI logs should preserve enough Cucumber output to make undefined steps easy to diagnose.
10. Undefined Step vs Pending Step
An undefined step and a pending step are related, but they are not the same. An undefined step has no matching step definition at all. Cucumber cannot find a Java method for it. A pending step has a matching Java method, but that method intentionally throws PendingException or otherwise marks the implementation as incomplete.
| Aspect | Undefined Step | Pending Step |
|---|---|---|
| Mapping | No matching method exists | Matching method exists |
| Generated snippet | Cucumber suggests one | Usually already copied |
| Execution | Cannot execute step code | Executes method but stops as pending |
| Typical fix | Add step definition | Replace PendingException with real code |
This distinction matters in reports. Undefined means Cucumber needs a mapping. Pending means the mapping exists but the implementation is incomplete. Treating both as the same issue can waste time during debugging.
11. Undefined Step vs Ambiguous Step
An undefined step means no step definition matches. An ambiguous step means more than one step definition matches. These are opposite mapping problems. In one case, Cucumber has no candidate. In the other, Cucumber has too many candidates and refuses to guess.
| Aspect | Undefined Step | Ambiguous Step |
|---|---|---|
| Cause | No matching definition | Multiple matching definitions |
| Cucumber behavior | Generates template | Reports ambiguity error |
| Primary fix | Create or correct mapping | Remove duplicate or overlapping mapping |
| Framework signal | Missing implementation | Uncontrolled vocabulary |
If a step is undefined, copying the generated template may be appropriate. If a step is ambiguous, copying another template will make the problem worse. The correct fix for ambiguity is to reduce overlap and ensure one step maps to one method.
12. Common Reason: No Step Definition Exists
The most obvious reason for an undefined step is that no step definition has been written yet. This is common during early development. A scenario is added to describe expected behavior, but the Java automation layer has not caught up.
Given the user is logged in
If no method exists with a matching annotation, Cucumber generates a snippet. This is a normal workflow when scenarios are intentionally written before implementation. The team should copy the snippet into the correct step class and implement it.
The mistake is ignoring the undefined step or allowing many undefined steps to accumulate. A few undefined steps during active development are manageable. A large number of undefined steps in a shared branch usually means feature files and automation code are no longer being developed together.
13. Common Reason: Wrong Annotation Text
Another common reason is annotation text that almost matches but not exactly. Cucumber matching is based on the expression inside the annotation. If the wording is different, the mapping fails.
Feature step:
Given the user logs in
Step definition:
@Given("user logs in")
public void userLogsIn() {
}
The annotation is missing "the". That small difference is enough to prevent matching. Cucumber will treat the feature step as undefined and may generate a new snippet with the exact wording from the feature file.
This is why many teams prefer copying generated snippets when adding new exact-match steps. It avoids transcription mistakes. It is also why teams should standardize phrases. If different scenarios use slightly different language for the same behavior, duplicate step definitions become likely.
14. Common Reason: Wrong Glue Package
Code Exists but Cucumber Cannot See It
A step definition can exist in the project and still be undefined if it is outside the configured glue package. Cucumber does not automatically scan every Java class. It searches the packages listed in the runner or platform configuration.
@CucumberOptions(
features = "src/test/resources/features",
glue = {"steps"}
)
If LoginSteps.java is actually under a package named stepdefinitions, Cucumber will not discover it when glue is set to steps. The annotation may be perfect, but the step remains undefined because discovery failed.
Wrong glue configuration is one of the most frequent real-world causes of confusion. Developers often inspect the annotation and wonder why it does not work, but the actual issue is package discovery. Always verify the runner glue path, package declaration, folder structure, and build configuration.
15. Common Reason: Step Class Outside Glue
Even when the glue package is broadly correct, individual classes can be placed in the wrong package. For example, the runner may search com.company.automation.steps, but a developer may accidentally create a new class under com.company.automation.temp or com.company.automation.pageobjects.
Configured glue:
com.company.automation.steps
Actual class:
com.company.automation.temp.RegistrationSteps
From Cucumber's perspective, RegistrationSteps does not exist. The fix is not to create another generated snippet elsewhere. The fix is to move the class into the correct package or update the glue configuration intentionally.
Good project structure reduces this problem. Step definition packages should be obvious, and developers should know where new step classes belong. Page object packages, utility packages, hook packages, and step packages should not be mixed.
16. Common Reason: Typographical Errors
Typos create undefined steps because annotation matching depends on text. A small grammar or spelling difference can break mapping.
Feature:
Given the user logs in
Step definition:
@Given("the users logs in")
public void userLogsIn() {
}
The extra "s" in users changes the annotation text. Cucumber does not know that the developer intended the same phrase. It simply sees no matching pattern.
Typographical errors are easy to introduce when step text is copied manually or when multiple team members use different wording. Code review should include Gherkin vocabulary review. Search before adding new step definitions. Use generated snippets when creating new mappings, but still edit the method body thoughtfully.
17. Common Reason: Parameter Mismatch
Undefined steps can also appear when a parameterized expression does not match the actual feature text. For example, {int} captures integer values, but it does not capture quoted text. {string} captures quoted strings, but it expects quotes in the step.
Feature:
Given the account balance is "500"
Step definition:
@Given("the account balance is {int}")
public void accountBalanceIs(int balance) {
}
The step uses a quoted value, but the expression expects an integer without quotes. The mapping may fail. The team should either change the feature step to use an unquoted number or change the expression to match the intended format.
Parameter mismatch is common when teams switch from exact text to reusable expressions. Cucumber Expressions are powerful, but they still require the feature text and annotation pattern to agree. The goal is not to make every step overly flexible. The goal is to capture the right dynamic data in a readable way.
18. Best Practices for Handling Undefined Steps
Turn Missing Mappings into Clean Code
The best practice is to handle undefined steps immediately and intentionally. If the scenario is new, copy the generated template into the correct step definition class. Then replace PendingException with real implementation or delegate to a suitable helper. Do not leave generated methods unfinished without tracking them.
Use the generated annotation as a starting point, but improve the Java method name if needed. Generated names are often mechanically derived from the step text. A clean method name helps future maintainers understand the class. At the same time, remember that the method name does not affect mapping.
Keep step definitions organized by business module. Login steps belong in login-related step classes. Registration steps belong in registration-related classes. Payment steps belong in payment-related classes. This makes it easier to find existing mappings before creating new ones.
19. Avoiding Duplicate Step Definitions
Undefined Step Generation can accidentally encourage duplication if developers copy snippets without checking for existing similar steps. A step may be undefined because the wording is slightly different from an existing step, not because the behavior is truly new. In that case, the better fix may be to standardize the feature wording and reuse the existing step.
For example, one file may say:
Given the user logs in
Another file may say:
Given the user is logged into the application
Both may describe the same behavior. Creating separate step definitions for both increases maintenance cost and can eventually create ambiguity. Before copying a generated snippet, search the existing step definitions for related phrases such as "logs in", "logged in", and "authenticated".
20. Real-Time Example: Registration Scenario
A registration feature provides a realistic example. The team writes this scenario:
Feature: User registration
Scenario: Successful registration
Given the user enters valid registration details
When the user submits the registration form
Then the account should be created
If no step definitions exist, Cucumber generates templates for all three steps:
@Given("the user enters valid registration details")
public void the_user_enters_valid_registration_details() {
throw new PendingException();
}
@When("the user submits the registration form")
public void the_user_submits_the_registration_form() {
throw new PendingException();
}
@Then("the account should be created")
public void the_account_should_be_created() {
throw new PendingException();
}
A clean implementation may delegate responsibilities:
public class RegistrationSteps {
@Given("the user enters valid registration details")
public void userEntersValidRegistrationDetails() {
registrationPage.enterValidDetails();
}
@When("the user submits the registration form")
public void userSubmitsRegistrationForm() {
registrationPage.submit();
}
@Then("the account should be created")
public void accountShouldBeCreated() {
registrationAssertions.verifyAccountCreated();
}
}
This final version removes PendingException and keeps the step methods focused. The feature file describes behavior. The step class maps behavior to Java. Page objects and assertion helpers perform the actual work.
21. Undefined Steps in CI/CD
Undefined steps in CI/CD pipelines usually indicate that new scenarios were merged without matching automation, the wrong runner configuration is being used, or build output does not include the expected test classes. In a healthy pipeline, undefined steps should be rare unless the team intentionally allows pending BDD work on a development branch.
When undefined steps appear only in CI and not locally, compare the local command with the CI command. Check Maven profiles, Gradle tasks, tag filters, runner classes, glue configuration, branch state, and compiled test output. Sometimes an IDE run uses one runner while CI uses another runner with different glue.
CI logs should preserve the generated snippets because they are useful for diagnosis. However, developers should not copy snippets blindly from CI and create duplicate classes. The correct fix may be package configuration, not new step code.
22. Common Mistakes with Generated Steps
A common mistake is leaving PendingException in committed code. This keeps the step in a permanently unfinished state. Another mistake is copying generated methods into the wrong package, which means Cucumber still cannot discover them. A third mistake is editing the generated annotation text unnecessarily and accidentally breaking the mapping.
Some teams also create one large step definition class by repeatedly pasting generated snippets into the same file. That works for a short time but becomes difficult to maintain. Generated snippets should be placed into the correct module-specific class, not dumped wherever the developer happens to be working.
Another mistake is treating every undefined step as a reason to add a new method. Sometimes the step is undefined because the scenario uses inconsistent vocabulary. In that case, rewriting the Gherkin to reuse an existing phrase is cleaner than adding another mapping.
23. Review Checklist for Undefined Steps
When you see an undefined step, ask whether the step represents a genuinely new business behavior. If yes, add a new step definition using the generated snippet as the starting point. If not, search for an existing step that expresses the same intent and update the scenario wording to reuse it.
- Does a matching step definition already exist with slightly different wording?
- Is the generated snippet being copied into the correct glue package?
- Is the annotation text still aligned with the feature step?
- Has PendingException been removed after implementation?
- Is the method body delegating to proper page objects, services, or helpers?
- Will the new step remain meaningful if the UI changes?
This checklist keeps Undefined Step Generation from becoming a source of duplication. The goal is not only to make the scenario run. The goal is to keep the Cucumber framework readable and stable over time.
24. Undefined Steps in the BDD Development Lifecycle
From New Scenario to Executable Specification
Undefined steps are not always a problem at the moment they appear. In a healthy BDD workflow, a new scenario may begin its life with undefined steps because the team has agreed on behavior before automation is complete. That is acceptable during active development. The important discipline is to treat undefined output as a clear to-do list, not as noise in the console. Each generated snippet represents a decision the team must make: reuse an existing step, standardize the wording, or implement a genuinely new mapping.
A practical lifecycle looks like this. First, the team writes or reviews the Gherkin scenario in business language. Second, the automation engineer runs the scenario and observes undefined snippets. Third, before copying anything, the engineer searches existing step definitions for similar behavior. Fourth, only missing behavior is implemented in the correct module-specific step class. Fifth, PendingException is removed and the method delegates to stable automation components. Finally, the scenario is run again to confirm that mapping problems are gone and any remaining failures represent real implementation or application behavior.
This lifecycle keeps Undefined Step Generation useful instead of chaotic. It prevents the framework from collecting many near-duplicate steps that mean the same thing. It also protects feature files from becoming inconsistent. When teams review generated snippets carefully, Cucumber remains a living documentation tool rather than a pile of disconnected test scripts.
25. Interview-Ready Summary
Short Explanation for Interviews
An undefined step occurs when Cucumber cannot find a matching step definition for a Gherkin step in the configured glue packages. Undefined Step Generation is the Cucumber feature that prints a suggested Java method template for that missing step. The generated snippet includes the correct annotation and usually throws PendingException until the developer adds the real implementation.
A strong interview answer should also mention common causes: missing step definition, wrong annotation text, typo, wrong glue package, step class outside glue, or parameter mismatch. You should explain that undefined is different from pending and ambiguous. Undefined means no mapping exists. Pending means a mapping exists but is incomplete. Ambiguous means more than one mapping exists.
- Undefined steps have no matching Java method.
- Cucumber generates snippets to help create missing step definitions.
- Generated methods usually contain PendingException until implemented.
- Wrong glue configuration can make existing methods appear undefined.
- Generated snippets should be used carefully to avoid duplicate mappings.
26. Golden Rule
The golden rule is simple: every new Gherkin step should either map to an existing step definition or be implemented from Cucumber's generated template in the correct glue package. Never leave steps undefined without a reason, and never paste generated snippets blindly without checking for existing vocabulary.
Undefined Step Generation is not just an error message. It is a development aid that supports the BDD workflow. Used well, it helps teams move from readable scenarios to executable automation quickly. Used carelessly, it can create duplicate step definitions, inconsistent language, and long-term maintenance problems. The best teams treat generated snippets as a starting point, then shape them into clean, domain-focused step definitions.