Cucumber JVM Architecture
Cucumber JVM is the Java-based implementation of Cucumber, and it is one of the most widely used BDD tools in Java automation projects. It allows scenarios written in Gherkin to be executed by Java code, which means business-readable feature files can drive Selenium UI tests, REST Assured API tests, database validations, service-level checks, and reporting workflows. The architecture is simple at a high level, but it becomes powerful when each layer has a clear responsibility.
In simple terms, the flow looks like this:
Feature File -> Cucumber JVM -> Step Definitions -> Automation Code -> Report
This flow is the foundation of Cucumber JVM architecture. The feature file describes the behavior. The Cucumber engine reads and parses that behavior. Step definitions connect Gherkin steps to Java methods. Java automation code performs the actual work. Reports summarize the result. When these layers are separated correctly, the framework remains readable, reusable, and maintainable. When the layers are mixed, Cucumber projects become difficult to scale.
1. What Is Cucumber JVM?
Cucumber JVM is the Java implementation of Cucumber. It is designed for teams that want to use Behavior Driven Development with Java-based test automation. A business analyst, tester, product owner, or developer can write behavior in a `.feature` file using Gherkin syntax. Cucumber JVM then executes those steps by matching them with Java methods known as step definitions.
The important idea is that Gherkin and Java play different roles. Gherkin describes behavior in a human-readable way. Java implements the executable logic. A scenario such as "Successful login" may be written in plain language, but the step definition behind it may create test data, open a browser, enter credentials, call page object methods, validate the dashboard, and attach screenshots to the report. The feature file stays focused on what the system should do, while Java code handles how the validation is performed.
Cucumber JVM fits naturally into Java automation ecosystems. It integrates with JUnit, TestNG, Maven, Gradle, Selenium WebDriver, REST Assured, Spring, reporting plugins, and CI/CD tools such as Jenkins or GitHub Actions. This is why it is popular in enterprise automation. It gives teams a business-readable test layer without forcing them to abandon their existing Java tooling.
Why Architecture Matters in Cucumber JVM
Many Cucumber projects start small. A team creates a feature file, writes a runner, adds step definitions, and executes a few scenarios. This works at first. The problem appears when the suite grows. Without architecture, step definitions become long, duplicate code appears, glue packages become confusing, hooks are misused, and reports become hard to interpret. A clear architecture prevents that drift.
Good Cucumber JVM architecture makes each layer responsible for one job. Feature files describe business behavior. Runners start execution. Glue code contains step definitions and hooks. Step definitions translate Gherkin into Java calls. Page classes, service classes, utilities, and clients perform the actual automation. Reports show results. This separation keeps the framework clean as more scenarios are added.
2. Main Architecture Layers
Cucumber JVM architecture can be understood as a layered system. Each layer receives information from the layer above it and delegates work to the layer below it. This layered design is what keeps BDD automation readable and maintainable. The most common layers are the feature file layer, Cucumber engine layer, step definition layer, automation logic layer, runner layer, hooks layer, and reporting layer.
The feature file layer is the business-readable layer. It contains `.feature` files written in Gherkin. The Cucumber engine layer reads those files, parses the syntax, identifies scenarios, and coordinates execution. The step definition layer maps each plain-language step to Java methods. The automation logic layer performs real UI, API, database, or utility work. The runner layer starts execution and controls options. Hooks manage setup and teardown. Plugins generate reports and attachments.
When a Cucumber JVM framework is healthy, these layers remain separate. A feature file should not contain Selenium details. A step definition should not contain hundreds of lines of WebDriver code. A page object should not know about feature file wording. A utility should not control scenario flow. Each layer should be simple enough to understand and change independently.
3. Feature File Layer
The feature file layer is where behavior is described. It contains `.feature` files written in Gherkin syntax using keywords such as Feature, Scenario, Given, When, Then, And, Background, and Scenario Outline. This layer should be understandable to both technical and non-technical stakeholders. It acts as acceptance criteria, living documentation, and the entry point for automated execution.
For example:
Feature: User Login
Scenario: Successful login
Given the user has valid credentials
When the user logs in
Then the user should see the dashboard
This scenario explains the behavior without exposing implementation details. It does not say which browser opens, which locator is used for the username field, which button is clicked, or how the dashboard is verified. Those details belong in lower layers. The feature file should describe the business expectation: a valid user can log in and reach the dashboard.
The purpose of the feature file layer is threefold. First, it defines expected behavior before or during development. Second, it provides acceptance criteria that can be reviewed by the team. Third, it works as living documentation after the automation is implemented. If feature files are written at the right level, they remain useful even when UI design or automation implementation changes.
Feature File Best Practices
Feature files should use business language, not automation language. Steps should describe intent rather than clicks and fields. Scenarios should validate one clear behavior. Background should be minimal. Scenario Outline should be used only when the same behavior is tested with different data. These practices keep the feature file layer clean and prevent Cucumber from becoming a plain-English Selenium script.
4. Cucumber Engine Layer
The Cucumber engine is the execution core of Cucumber JVM. It reads feature files, parses Gherkin syntax, identifies scenarios and steps, matches those steps with Java methods, executes the matching code, records outcomes, and passes information to reporting plugins. Most users do not directly write code inside the engine, but understanding its role helps diagnose execution problems.
When execution starts, Cucumber scans the configured feature file locations. It reads each `.feature` file and parses the Gherkin structure. It identifies features, scenarios, scenario outlines, examples, tags, backgrounds, and steps. Then it uses glue configuration to find step definition methods and hooks. For each step, it attempts to find a Java method whose annotation pattern matches the Gherkin text.
If a step cannot be matched, Cucumber reports it as undefined. If more than one method matches the same step, Cucumber reports ambiguity. If a matching method throws an exception, the step fails and the scenario is marked failed. This matching and execution process is central to Cucumber JVM architecture.
Why Step Matching Matters
Step matching is where feature language meets Java code. A clean framework uses consistent wording and avoids duplicate step definitions. If teams use inconsistent vocabulary, step libraries grow unnecessarily. If patterns are too generic, ambiguity appears. Strong architecture includes discipline around step wording and glue organization.
5. Step Definition Layer
Step definitions connect Gherkin steps with Java code. They are Java methods annotated with Cucumber annotations such as `@Given`, `@When`, and `@Then`. Each annotation contains a pattern that matches text in the feature file. When Cucumber reaches a matching step during execution, it calls the Java method.
Example:
@Given("the user has valid credentials")
public void userHasValidCredentials() {
// setup test data
}
The step definition layer is a bridge. It should translate business language into automation actions. It should not become the place where all automation code lives. A step definition may call a page object, service class, API client, test data helper, assertion utility, or workflow class. Keeping step definitions thin improves readability and reuse.
A common mistake is putting heavy Selenium or REST Assured logic directly inside step definitions. That works for a few scenarios, but it quickly becomes hard to maintain. Step definitions become long, duplicated, and difficult to debug. A better design is:
Feature File -> Step Definition -> Page/Service Layer -> Utility Layer
Thin Step Definitions
Thin step definitions are easier to understand. They should read like a translation of the Gherkin step. For example, a step definition for "When the user logs in" can call `loginPage.loginWith(validUser)` or `loginAction.login(validUser)`. The page object handles locators and WebDriver actions. The step definition remains focused on scenario flow.
6. Automation Logic Layer
The automation logic layer is where real execution happens. In a UI automation framework, this layer may include Selenium WebDriver code, page objects, component objects, waits, locators, and browser utilities. In an API automation framework, it may include REST Assured clients, request builders, response validators, authentication helpers, and DTO classes. In a hybrid framework, it may include UI, API, database, and test data utilities.
This layer should be organized by responsibility. Page classes should contain locators and page-specific behavior. Service classes should contain API calls and response handling. Utility classes should contain reusable support code such as configuration reading, file handling, date generation, random data generation, and reporting helpers. Test data classes should manage data creation and cleanup.
The automation layer should not leak into feature files. A feature file should not mention XPath, WebDriver, status codes, SQL queries, or implementation-specific names unless those are truly part of the requirement. Technical validation can still happen, but it should happen in Java code behind meaningful business steps.
Separation of Concerns
Separation of concerns is the key design principle here. Feature files describe behavior. Step definitions coordinate execution. Page and service classes perform technical actions. Utilities support common needs. Reports communicate outcomes. When these responsibilities stay separate, changes in one area do not break the entire framework.
7. Runner Layer
The runner layer starts Cucumber execution. In Cucumber JVM projects, runners are commonly integrated with JUnit or TestNG. The runner class tells Cucumber where feature files are located, where glue code is located, which tags to include or exclude, which plugins to use, and where reports should be generated.
Example using JUnit:
@RunWith(Cucumber.class)
@CucumberOptions(
features = "src/test/resources/features",
glue = "stepdefinitions",
plugin = {"pretty", "html:target/cucumber-report.html"}
)
public class TestRunner {
}
The runner controls important execution options. The `features` option points to feature file locations. The `glue` option tells Cucumber where to find step definitions and hooks. The `plugin` option controls reporting output. Tags can be used to run subsets of scenarios, such as smoke, regression, API, UI, or sprint-specific tests.
In TestNG-based frameworks, the runner may extend an abstract Cucumber TestNG class and use TestNG XML for suite execution. In JUnit 5 setups, Cucumber can be integrated through the JUnit Platform engine. The exact runner style depends on the project, but the architectural role is the same: it starts and configures execution.
8. Complete Cucumber JVM Execution Flow
The complete execution flow begins when the runner starts. Cucumber reads the configured options and scans the feature file path. The Gherkin parser reads each feature file and converts it into executable scenario structures. Cucumber then checks tags, applies filtering rules, prepares hooks, and starts scenario execution.
For each scenario, Cucumber executes applicable Before hooks. It then executes each step in order. Each step is matched with a Java step definition. The step definition calls automation logic such as page objects, API clients, database helpers, or utilities. If all steps pass, After hooks run and the scenario is marked passed. If a step fails, Cucumber records the failure, skips remaining steps as appropriate, runs teardown hooks, and sends the result to reporting plugins.
The flow can be summarized as:
- Runner starts execution.
- Cucumber scans feature files.
- Gherkin parser reads scenarios.
- Each step is matched with a step definition.
- Step definition executes Java code.
- Java code interacts with UI, API, database, or utilities.
- Scenario result is recorded.
- Report is generated.
Understanding this flow helps troubleshoot common issues. Undefined steps usually point to missing or incorrect glue. Ambiguous steps usually point to duplicate patterns. Hook issues often come from glue configuration or lifecycle misunderstanding. Reporting issues usually come from plugin configuration.
9. Typical Project Structure
A well-structured Cucumber JVM project separates Java code and feature files clearly. In Maven-style projects, Java test code usually lives under `src/test/java`, while feature files live under `src/test/resources`. This structure keeps executable code and readable behavior files organized according to common Java conventions.
src/test/java
├── runners
│ └── TestRunner.java
├── stepdefinitions
│ └── LoginSteps.java
├── pages
│ └── LoginPage.java
├── hooks
│ └── Hooks.java
└── utilities
├── DriverFactory.java
└── ConfigReader.java
src/test/resources
└── features
└── login.feature
The runner package contains execution entry points. The stepdefinitions package contains Java mappings for Gherkin steps. The pages package contains page object classes for Selenium-based UI automation. The hooks package contains setup and teardown logic. The utilities package contains reusable support code such as driver management, configuration, file handling, waits, and reporting helpers.
Scaling the Structure
As the project grows, packages may be organized by module or domain. For example, a large e-commerce project may have feature folders for login, catalog, cart, checkout, payment, and order history. Java packages may follow similar modules. The goal is to make it easy to find related feature files, step definitions, page objects, and utilities without creating tight coupling.
Another practical scaling rule is to avoid creating packages only by technical type when the project becomes very large. A small framework can work with one pages package, one stepdefinitions package, and one utilities package. A larger product may need module-based organization so that checkout pages, checkout steps, checkout services, and checkout test data helpers are easy to find together. The structure should help the team navigate the product, not simply satisfy a folder convention.
Teams should also agree on naming conventions early. Runner classes should clearly describe their purpose, such as SmokeRunner, RegressionRunner, ApiRunner, or TestRunner. Step definition classes should map to a feature area, not to a random collection of steps. Utility classes should have focused names such as ConfigReader, DriverFactory, WaitUtils, ScreenshotUtils, or JsonReader. Clear names reduce onboarding time and prevent duplicate code.
10. Key Components in Cucumber JVM
Cucumber JVM contains several important components, each with a clear purpose. Understanding these components helps explain the architecture in interviews and helps maintain real frameworks.
| Component | Purpose |
|---|---|
| Feature File | Defines business behavior in Gherkin. |
| Gherkin Parser | Reads `.feature` files and understands scenario structure. |
| Step Definitions | Map Gherkin steps to Java methods. |
| Glue Code | Packages where Cucumber searches for step definitions and hooks. |
| Runner | Starts execution and provides configuration. |
| Hooks | Run setup, teardown, screenshots, and cleanup around scenarios. |
| Plugins | Generate console, HTML, JSON, JUnit, and custom reports. |
| Automation Layer | Contains Selenium, API, database, and utility logic. |
11. Role of Glue Code
Glue code tells Cucumber where step definitions and hooks are located. In runner configuration, glue is usually defined as one or more package names. Cucumber scans those packages to find annotated Java methods.
glue = {"stepdefinitions", "hooks"}
If the glue path is wrong, Cucumber cannot find step definitions or hooks. Steps remain undefined, hooks do not run, and execution may fail before useful automation begins. Glue configuration is one of the most common setup issues in Cucumber JVM projects.
Glue should be specific enough to avoid scanning unnecessary packages but broad enough to include all step definitions and hooks needed for the suite. Some teams place steps and hooks under a common root package, such as `com.company.project.bdd`, and use that root as glue. Others list separate packages. The right choice depends on project size and organization.
Glue Code and Duplicate Steps
Glue organization also affects duplicate step management. If multiple packages contain step definitions with similar patterns, ambiguity can occur. Keeping step definitions organized by domain and maintaining consistent vocabulary reduces this risk.
In real frameworks, glue problems often appear after refactoring. A team may move step definitions to a new package but forget to update the runner. The feature file remains correct, the Java method exists, but Cucumber reports the step as undefined because the glue path no longer points to it. This is why runner configuration should be reviewed whenever packages are reorganized.
12. Role of Hooks in Architecture
Hooks manage the scenario lifecycle. Cucumber JVM supports hooks such as Before and After, which run before and after scenarios. Hooks are commonly used for browser setup, driver cleanup, screenshots on failure, test data creation, test data cleanup, report attachments, and logging.
@Before
public void setup() {
// open browser
}
@After
public void tearDown() {
// close browser
}
Hooks keep repetitive setup and cleanup out of scenarios. A feature file should not say "Given the browser is opened" unless opening the browser is part of the behavior, which is rare. Browser setup is an automation concern. It belongs in hooks or framework setup code.
Hooks should be used carefully. Overloaded hooks can hide important context and make scenarios harder to understand. For example, if a Before hook creates a user, logs in, creates a cart, and adds payment data for every scenario, readers may not know which conditions actually matter. Hooks are best for technical setup and cleanup, while business-specific assumptions should be visible in scenarios.
Tagged Hooks
Cucumber JVM supports tagged hooks, which run only for scenarios with specific tags. This is useful when API scenarios need different setup from UI scenarios, or when database cleanup is needed only for certain flows. Tagged hooks help avoid one-size-fits-all setup that slows the whole suite.
Hooks are also useful for failure handling. In UI automation, an After hook can check whether a scenario failed and attach a screenshot to the report. It can also attach browser logs, current URL, page title, or test data identifiers. This makes debugging faster because the report contains context from the moment of failure. The hook should still remain focused; it should collect diagnostics and clean up resources, not implement business flow.
13. Cucumber JVM with Selenium Architecture
In Selenium-based Cucumber JVM frameworks, the execution flow usually moves from feature file to runner, from runner to step definitions, from step definitions to page object classes, from page objects to WebDriver, and finally to the browser.
Feature File
↓
Runner
↓
Step Definitions
↓
Page Object Classes
↓
Selenium WebDriver
↓
Browser
The best practice is to keep feature files free from UI details. A scenario should say "When the user logs in" rather than "When the user enters username, enters password, and clicks the Login button." The step definition can call a page method such as `loginPage.login(username, password)`. The page class contains locators and WebDriver actions.
This design reduces maintenance. If the login page changes, the feature file and step definition may remain unchanged. Only the page object needs updating. This is the core value of layering in Selenium Cucumber frameworks.
Driver Management
Most Selenium Cucumber JVM frameworks include a DriverFactory or WebDriverManager utility. This class creates browser instances, manages thread-local drivers for parallel execution, configures browser options, and closes drivers after scenarios. Keeping driver logic centralized prevents duplication and makes browser changes easier.
For parallel execution, driver management becomes even more important. Each scenario or thread must receive the correct WebDriver instance. If driver instances are shared incorrectly, tests interfere with each other, windows close unexpectedly, and results become flaky. Many Java frameworks solve this with ThreadLocal WebDriver storage, combined with hooks that create and quit drivers per scenario.
14. Cucumber JVM with API Testing Architecture
Cucumber JVM is not limited to UI automation. It can also drive API testing with libraries such as REST Assured. In API-focused architecture, feature files describe API behavior or business outcomes, step definitions call service classes, service classes build and send requests, and assertions validate responses.
Feature File
↓
Step Definitions
↓
API Client / Service Class
↓
REST Assured
↓
API Response Validation
For example, a feature file may say, "When the customer requests order details" and "Then the order details should be returned." The step definition can call an OrderService class, which uses REST Assured to send the request. The technical validation can check status code, response body, schema, headers, and database state behind the scenes.
The best practice is the same as UI architecture: keep technical details out of Gherkin unless they are part of the behavior being specified. Feature files should describe API behavior in language meaningful to the intended audience. Request and response logic should stay in service classes.
API architecture is often faster and more stable than UI execution, so many teams use API calls for test setup even in UI scenarios. For example, a UI scenario may need an existing customer, an active cart, or a saved payment method. Creating that state through APIs can be faster than navigating through multiple screens. The feature file can still describe the state in business language, while the Java code chooses the efficient setup path.
15. Reporting and Plugin Layer
The reporting layer communicates execution results. Cucumber JVM supports plugins such as pretty console output, HTML reports, JSON reports, JUnit XML reports, and third-party report integrations. Reports help teams understand which scenarios passed, failed, were skipped, or produced errors.
Runner configuration controls reporting plugins. For example, an HTML plugin may generate a browser-readable report under `target`. A JSON plugin may produce a file consumed by another reporting tool. JUnit XML may be used by CI servers. Screenshots and logs can be attached through hooks or plugin integrations.
Good reports depend on good scenario design. If scenario names are vague or too technical, reports are less useful. If scenarios validate one clear behavior, reports provide actionable feedback. Architecture and Gherkin quality are connected.
Reports are especially important in CI/CD pipelines. A Jenkins or GitHub Actions job may run hundreds of scenarios without a human watching the browser. The report becomes the main communication artifact. Clear scenario names, useful failure messages, screenshots, logs, and structured output help the team understand failures quickly. A strong reporting layer reduces the time spent reproducing issues locally.
16. Common Architecture Mistakes
Several mistakes appear repeatedly in Cucumber JVM projects. One mistake is writing Selenium code directly inside feature files. Feature files should never contain locators, clicks, browser setup, or technical verification details. Another mistake is putting too much logic inside step definitions. Step definitions should coordinate, not become giant automation classes.
Wrong glue path configuration is another common issue. If glue does not point to the correct packages, Cucumber cannot find steps or hooks. Duplicate step definitions also create problems because Cucumber may find multiple matching methods. Hardcoding test data makes scenarios fragile and environment-dependent. Not using hooks properly can leave browsers open, data dirty, or reports incomplete.
- Writing Selenium code directly inside feature files.
- Putting too much logic inside step definitions.
- Using wrong glue path configuration.
- Creating duplicate or ambiguous step definitions.
- Mixing pages, utilities, steps, and assertions without clear boundaries.
- Hardcoding test data in feature files or step definitions.
- Not using hooks correctly for setup, teardown, screenshots, and cleanup.
How to Fix Architecture Drift
Architecture drift should be addressed gradually. Move Selenium code from step definitions into page objects. Move API calls into service classes. Centralize driver setup. Standardize package naming. Refactor duplicate steps. Review feature files for UI-driven wording. Small improvements made consistently will restore structure without requiring a complete rewrite.
17. Interview-Ready Summary
In interviews, a strong explanation of Cucumber JVM architecture should cover the main layers and their responsibilities. Cucumber JVM is the Java implementation of Cucumber. It executes Gherkin feature files using Java step definitions. The main layers are feature files, runner, Cucumber engine, glue code, step definitions, hooks, automation logic, and reports.
You should explain that the runner starts execution and provides configuration through options such as features, glue, tags, and plugins. Glue connects Gherkin steps to Java methods. Step definitions should be thin and reusable. Page objects, service classes, utilities, and API clients should contain real automation logic. Hooks manage scenario lifecycle tasks such as browser setup, teardown, screenshots, and cleanup.
- Cucumber JVM is the Java implementation of Cucumber.
- It executes Gherkin feature files using Java step definitions.
- Main layers include feature files, runner, step definitions, hooks, automation logic, and reports.
- Runner controls execution using Cucumber options.
- Glue connects feature steps to Java methods.
- Good architecture keeps step definitions thin and reusable.
18. Golden Rule
The golden rule of Cucumber JVM architecture is simple: feature files describe behavior; Java code implements execution. If the feature file starts describing Selenium actions, API implementation, database queries, or framework mechanics, the architecture is leaking. If step definitions contain all automation logic, the architecture is too shallow. If page and service classes are reusable and feature files remain business-readable, the architecture is healthy.
A scalable Cucumber JVM framework is not created by adding more packages randomly. It is created by assigning clear responsibilities to each layer. Feature files communicate intent. Runners configure execution. Glue locates steps and hooks. Step definitions translate behavior. Automation classes do technical work. Hooks manage lifecycle. Reports communicate outcomes. When each layer does its job, Cucumber JVM becomes a strong BDD framework rather than a brittle automation wrapper.