Project Folder Structure in Cucumber JVM

A project folder structure is the logical organization of all files and directories in a Cucumber automation project. It defines where feature files live, where Java step definitions are written, where runners are configured, where hooks are maintained, where page objects are stored, where utilities belong, and where configuration, reports, test data, API clients, database helpers, and model classes should be placed. In a small demo project, folder structure may look like a minor detail. In a real framework used by a team, it becomes one of the most important foundations of maintainability.

A good Cucumber JVM folder structure helps the team understand the framework without reading every file. It improves maintainability because responsibilities are separated. It reduces code duplication because reusable logic has a clear home. It supports scalability because new modules can be added without breaking existing areas. It also supports collaboration because testers, developers, and automation engineers can follow the same conventions. When the structure is poor, every change becomes harder. People place code wherever it is convenient, utilities become large, step definitions become overloaded, and feature files become difficult to connect with implementation.

The guiding principle behind a good structure is separation of concerns. Each package should have a single responsibility. Feature files should describe behavior. Step definitions should connect Gherkin to Java. Page objects should contain UI locators and actions. Service classes should contain API behavior. Utilities should contain reusable technical helpers. Configuration should be externalized. Reports should be generated into build folders. Test data should not be hardcoded inside step definitions. This article explains how to design that structure in a practical Cucumber JVM framework.

1. What Is a Project Folder Structure?

A project folder structure is the way a software project organizes its files, packages, directories, and resources. In Cucumber JVM automation, the structure usually follows Java and Maven conventions while also supporting BDD-specific needs. This means the project must provide space for feature files, runners, step definitions, hooks, page objects, utilities, configuration, reports, API clients, database helpers, and test data.

The structure should make the framework predictable. If someone wants to find a runner class, they should know where to look. If someone wants to update a Selenium locator, they should know it belongs in a page object, not in a step definition. If someone wants to change the browser from Chrome to Edge, they should know that configuration should come from a config file or framework configuration class, not from hardcoded values scattered across many tests.

A good folder structure also communicates framework design. It shows whether the team values maintainability, reuse, and clear boundaries. A project where everything is placed in one package may execute tests, but it will become difficult to scale. A project where files are grouped by responsibility is easier to extend and easier to review.

Why Folder Structure Matters in Real Projects

In real projects, automation frameworks are touched by many people over months or years. New scenarios are added, old pages change, APIs evolve, environments differ, test data grows, and reports become more important. A clear structure reduces the time needed to understand the framework. It also lowers the risk of duplicate utilities, inconsistent step definitions, and hidden dependencies.

2. Standard Maven Project Structure

Most Cucumber JVM projects follow the Maven standard directory layout. Maven is widely used in Java automation because it provides dependency management, build lifecycle, plugin configuration, test execution support, and integration with CI/CD tools. Even if the project uses Gradle instead of Maven, the same separation between main code, test code, and resources usually applies.

Project
|
|-- src
|   |-- main
|   |   |-- java
|   |   |-- resources
|   |
|   |-- test
|       |-- java
|       |-- resources
|
|-- target
|-- pom.xml
|-- README.md

The `src/main` folder is used for production code if the project contains application code or reusable framework libraries. In many pure automation repositories, this folder may be empty or minimal. The `src/test` folder contains test automation code and test resources. The `target` folder is generated by Maven and contains compiled classes, reports, logs, and other generated output. The `pom.xml` file defines dependencies, plugins, and build configuration. The `README.md` file explains how to set up and run the framework.

Following the Maven layout makes the project familiar to Java developers and automation engineers. It also helps tools work naturally. IDEs understand the source and resource folders. Maven knows where to compile code and where to find resources. CI/CD pipelines can run test goals without custom folder hacks. This consistency becomes valuable as the project grows.

Why Feature Files Usually Go Under Test Resources

In Cucumber JVM, feature files are commonly placed under `src/test/resources/features`. They are not Java source files, so they should not be placed under `src/test/java`. Keeping feature files under resources makes them available on the test classpath and keeps behavior documentation separate from implementation code.

3. Recommended Cucumber Project Structure

A practical Cucumber JVM framework usually extends the Maven structure with packages and folders dedicated to BDD automation responsibilities. The exact structure varies by team, but the following layout is a strong starting point for UI, API, and hybrid automation frameworks.

Project
|
|-- src
|   |-- test
|   |   |-- java
|   |   |   |-- runners
|   |   |   |-- stepdefinitions
|   |   |   |-- hooks
|   |   |   |-- pages
|   |   |   |-- utilities
|   |   |   |-- config
|   |   |   |-- constants
|   |   |   |-- factory
|   |   |   |-- managers
|   |   |   |-- listeners
|   |   |   |-- reports
|   |   |   |-- api
|   |   |   |-- database
|   |   |   |-- models
|   |   |
|   |   |-- resources
|   |       |-- features
|   |       |-- testdata
|   |       |-- config
|   |       |-- log4j2.xml
|
|-- target
|-- pom.xml
|-- README.md

This layout separates responsibility clearly. Java automation code lives under `src/test/java`. Non-Java resources live under `src/test/resources`. Runners start execution. Step definitions map Gherkin to Java. Hooks manage lifecycle events. Pages contain UI-specific details. Utilities provide reusable helpers. Config stores framework settings. Constants avoid repeated hardcoded values. Factories create objects such as WebDriver instances. Managers centralize shared resources. Listeners support execution events. Reports handle custom reporting. API and database packages support non-UI validation. Models represent data objects.

This structure does not mean every project must contain every package from day one. A small UI-only project may not need `api` or `database` packages immediately. A pure API BDD project may not need `pages`. The key is to create packages when they represent real responsibility, not just to make the project look large.

A good rule is to start simple and grow intentionally. If the framework has only a few UI scenarios, runners, stepdefinitions, hooks, pages, utilities, and resources may be enough. When API setup becomes common, add an api package. When database validation becomes repeated, add a database package. When reporting customization grows, add a reports package. This keeps the project understandable instead of forcing new team members to navigate unused folders.

4. Runners Package

The runners package contains classes that start Cucumber execution. A runner class defines where feature files are located, where glue code is located, which tags should run, and which reporting plugins should be enabled. In a JUnit-based framework, the runner may use Cucumber annotations. In a TestNG-based framework, the runner may extend a TestNG Cucumber base class.

runners
   LoginRunner.java
   RegressionRunner.java
   SmokeRunner.java

Runner classes should be simple and focused. They should not contain business logic, WebDriver logic, test data logic, or assertions. Their purpose is configuration and execution control. A runner may define `features`, `glue`, `plugin`, `tags`, and other options. It may also support different suites, such as smoke tests, regression tests, API tests, or UI tests.

Clear runner naming helps the team understand execution intent. A class named `SmokeRunner` should run smoke scenarios. A class named `RegressionRunner` should run the regression suite. If a project uses Maven profiles or CI parameters to control tags, the runner can remain generic while execution options are passed from the build pipeline.

Teams should avoid creating too many runner classes for small differences. If every tag gets its own runner, runner maintenance becomes noisy. In many frameworks, one or two runner classes combined with Maven parameters, TestNG XML, or CI variables are enough. The runner package should make execution easier, not become another source of duplication.

5. Step Definitions Package

The stepdefinitions package contains Java methods mapped to Gherkin steps. Each method is annotated with `@Given`, `@When`, `@Then`, or related Cucumber annotations. Step definitions receive the plain-language steps from feature files and translate them into executable Java calls.

stepdefinitions
   LoginSteps.java
   RegistrationSteps.java
   PaymentSteps.java

Step definitions should be thin. Their responsibility is orchestration, not heavy implementation. A step definition may call a page object method, an API service method, a database helper, a test data factory, or an assertion utility. It should not contain long Selenium scripts, raw API request construction, complex business logic, or repeated utility code.

For example, a step definition for login should not locate every element and type into every field directly. It should call a method such as `loginPage.login(username, password)` or `loginAction.loginAs(validUser)`. This keeps the step definition readable and allows the page object or action layer to handle UI details.

What to Avoid in Step Definitions

Avoid Selenium locators, API request construction, hardcoded data, complex conditional logic, and duplicated assertions inside step definitions. If step definitions become too large, the framework becomes difficult to maintain. Thin step definitions are one of the strongest signs of a clean Cucumber JVM architecture.

6. Hooks Package

The hooks package contains lifecycle methods that run before or after scenarios. Hooks are commonly used for browser initialization, browser closing, screenshot capture, test data cleanup, logging, and report attachments. They help keep repetitive technical setup out of feature files and step definitions.

hooks
   Hooks.java

A typical hooks class may include setup and teardown methods:

@Before
public void setup() {
    // open browser or prepare test context
}

@After
public void teardown() {
    // close browser or cleanup test context
}

Hooks should be used carefully. They are excellent for technical lifecycle concerns, but they should not hide important business context. If every scenario requires a browser, opening the browser in a hook is fine. If only some scenarios require a user with a cart and payment method, that business condition should be visible in those scenarios or handled through clear tagged setup.

Tagged Hooks

Tagged hooks allow setup or teardown to run only for scenarios with specific tags. This is useful when UI scenarios need browser setup, API scenarios need token setup, or database scenarios need cleanup. Tagged hooks prevent unnecessary setup from slowing the entire test suite.

7. Pages Package

The pages package contains Page Object classes for UI automation. A page object represents a screen, page, or sometimes a reusable component. It contains locators, WebDriver actions, and page-specific methods. The goal is to keep UI details out of step definitions and feature files.

pages
   LoginPage.java
   HomePage.java
   CheckoutPage.java

A LoginPage class may contain methods such as `enterUsername`, `enterPassword`, `clickLogin`, and `login`. A higher-level method such as `login(username, password)` is often more useful because it hides the internal UI sequence. Step definitions can call this method without knowing locators or button names.

Page objects should contain UI behavior, not business decision logic. For example, a CheckoutPage can click the place order button and read confirmation text, but it should not decide whether a payment rule is valid. Business decisions should live in the application or in domain-level validation helpers, not in page object classes.

Page Objects vs Step Definitions

Step definitions describe scenario flow. Page objects perform screen interactions. Mixing these responsibilities leads to duplicated locators, repeated waits, and fragile automation. Keeping UI details in page objects makes maintenance easier when the interface changes.

8. Utilities Package

The utilities package contains reusable helper classes. These classes support common technical needs across the framework. Examples include configuration reading, Excel reading, JSON reading, wait utilities, screenshot utilities, file utilities, date utilities, random data generators, and logging helpers.

utilities
   ConfigReader.java
   ExcelReader.java
   WaitUtils.java
   ScreenshotUtils.java
   JsonReader.java

The purpose of utilities is to avoid duplicating common code. If many tests need to read a configuration value, that logic should live in one ConfigReader. If many page objects need explicit waits, those waits should be centralized. If screenshots are attached on failure, ScreenshotUtils can handle file naming, capture, and attachment logic.

Utilities should remain focused. A common mistake is creating a giant utility class that does everything. This kind of God class becomes difficult to understand and risky to modify. It is better to create small utility classes with clear names and responsibilities.

Utility classes should also avoid depending on scenario-specific context unless that is their purpose. For example, a generic WaitUtils class should not know about login pages, payment screens, or user roles. It should provide reusable wait behavior. This keeps utilities reusable across modules and prevents hidden coupling between unrelated parts of the framework.

9. Config and Constants Packages

Configuration controls how the framework runs. It may include browser name, environment, base URL, timeout values, headless mode, remote execution settings, report paths, retry flags, credentials strategy, and API endpoints. Configuration values should not be hardcoded inside step definitions or page objects.

config
   FrameworkConfig.java
   config.properties

A properties file might contain:

browser=chrome
url=https://example.com
timeout=20
headless=false

The constants package stores values that should not be repeated across the codebase. Examples include base paths, default timeouts, file extensions, date formats, report names, and common keys. Constants help avoid magic strings and make changes safer.

constants
   FrameworkConstants.java
   ApiConstants.java

Externalizing Configuration

Externalized configuration makes the same framework usable across local machines, QA environments, staging environments, and CI pipelines. The code should not need to change when the environment changes. Only configuration values should change.

10. Factory and Managers Packages

The factory package contains object creation logic. In Selenium Cucumber frameworks, DriverFactory is one of the most common factory classes. It creates WebDriver instances based on browser configuration, sets browser options, supports headless execution, and may support local or remote execution.

factory
   DriverFactory.java

For parallel execution, DriverFactory often works with ThreadLocal WebDriver storage. This ensures each scenario or thread receives the correct browser instance. Without proper driver management, parallel tests can interfere with each other, close each other's browsers, or produce unstable results.

The managers package centralizes shared resource management. Examples include DriverManager, PageObjectManager, FileReaderManager, and TestDataManager. These classes help control object lifecycle and reduce repeated creation logic.

managers
   DriverManager.java
   PageObjectManager.java
   FileReaderManager.java

Managers should simplify framework usage, not hide too much complexity. If a manager becomes a large class controlling unrelated resources, it should be split. The same separation of concerns principle applies here.

11. Listeners and Reports Packages

Listeners are commonly used with TestNG or reporting tools. They can respond to test execution events such as test start, test success, test failure, skipped tests, and suite completion. In Cucumber projects, hooks handle scenario lifecycle, while listeners may handle framework-level or TestNG-level events.

listeners
   TestListener.java
   RetryListener.java

The reports package contains reporting utilities. It may include ExtentManager, ReportGenerator, screenshot attachment helpers, or custom report formatting classes. Reports are important because automation results must be understandable to the team. A clean reporting structure keeps report setup separate from scenario logic.

reports
   ExtentManager.java
   ReportGenerator.java

Reporting code should not be scattered across step definitions. If every step definition manually creates report entries, the framework becomes noisy and hard to update. Centralized reporting utilities make it easier to change report tools or formats later.

12. API, Database, and Models Packages

Modern Cucumber JVM frameworks often include more than UI automation. API automation may be used for direct validation, test setup, cleanup, or hybrid UI/API flows. The api package can contain service classes such as UserService, OrderService, PaymentService, or AuthService.

api
   UserService.java
   OrderService.java
   AuthService.java

API service classes should handle REST calls, request creation, response parsing, authentication headers, and response validation helpers. Step definitions should call these services rather than constructing requests directly.

The database package contains database-related classes such as DBConnection, Queries, and repository helpers. These classes may support test data setup, cleanup, or validation. Database logic should be centralized and used carefully so that tests do not become tightly coupled to internal implementation details.

database
   DBConnection.java
   Queries.java

The models package contains Java POJOs used for data objects, JSON serialization, request bodies, response bodies, and test data representation. Examples include User, LoginRequest, Employee, Order, and PaymentDetails.

models
   User.java
   LoginRequest.java
   Employee.java

Why Models Improve Clean Code

Models reduce the need to pass loose maps or long parameter lists around the framework. They make data structures explicit and easier to validate. In API automation, models also support cleaner serialization and deserialization.

13. Resources Folder

The resources folder contains non-Java files used by the automation framework. In a Cucumber JVM project, the most important resource folder is usually `features`, which stores `.feature` files. Other resource folders may store test data, configuration files, logging configuration, JSON schemas, templates, and environment-specific files.

src/test/resources
|-- features
|   |-- login.feature
|   |-- registration.feature
|   |-- payment.feature
|
|-- testdata
|   |-- users.xlsx
|   |-- employee.json
|
|-- config
|   |-- config.properties
|
|-- log4j2.xml

Feature files belong in resources because they are not Java code. Test data belongs in resources because it should be easy to change without modifying source classes. Logging configuration such as log4j2.xml also belongs here so it can be loaded at runtime.

Teams should keep resource folders organized. A testdata folder may contain separate subfolders for JSON, Excel, CSV, XML, or module-specific data. Feature folders may be grouped by product area. The structure should make files easy to find without becoming overly complex.

14. Target Folder

The target folder is generated automatically by Maven. It contains compiled classes, reports, screenshots, logs, temporary files, and other build output. Because target is generated, it is usually ignored in Git. Committing target files creates unnecessary repository noise and can lead to stale reports or environment-specific artifacts being shared accidentally.

Reports generated by Cucumber, Extent Reports, Surefire, or Failsafe often appear under target. Screenshots captured on failure may also be stored there. This is appropriate because target represents output from execution, not source code. If the project needs permanent sample reports, they should be stored separately and intentionally.

15. pom.xml

The pom.xml file is the heart of a Maven-based Cucumber JVM framework. It defines project metadata, dependencies, plugins, build configuration, Java version, test execution plugins, and reporting plugins. Without a clean pom.xml, the framework becomes difficult to build and run consistently across machines.

Common dependencies include Cucumber JVM, Selenium, JUnit or TestNG, WebDriverManager, REST Assured, Log4j, Extent Reports, JSON libraries, Apache POI for Excel handling, and database drivers when needed. Common plugins include Maven Surefire, Maven Failsafe, compiler plugins, and reporting plugins.

The pom.xml should be maintained carefully. Avoid unused dependencies, conflicting versions, and hardcoded local paths. Use properties for versions where practical. A clean pom.xml helps CI/CD pipelines run reliably.

16. README.md

The README.md file documents the framework. It should explain the purpose of the project, prerequisites, setup steps, dependencies, commands to run tests, tag usage, reporting instructions, environment configuration, and troubleshooting notes. A good README reduces onboarding time and helps team members run the suite without asking the same questions repeatedly.

For example, README should show commands such as how to run smoke tests, regression tests, or a specific tag. It should explain where reports are generated and where screenshots are stored. It should also mention required tools such as Java version, Maven version, browser versions, and any environment variables.

Documentation is part of framework quality. A technically good folder structure is less useful if nobody knows how to use it. README connects the structure to daily usage.

README should also explain the folder structure briefly. A short section describing what belongs in runners, stepdefinitions, hooks, pages, utilities, resources, and reports can prevent future misuse. This is especially helpful when new testers join the project or when developers contribute automation for the first time.

17. Example Real Project Structure

A real project structure may look like this:

CucumberFramework
|
|-- src
|   |-- test
|       |-- java
|       |   |-- runners
|       |   |   |-- LoginRunner.java
|       |   |
|       |   |-- hooks
|       |   |   |-- Hooks.java
|       |   |
|       |   |-- stepdefinitions
|       |   |   |-- LoginSteps.java
|       |   |
|       |   |-- pages
|       |   |   |-- LoginPage.java
|       |   |   |-- HomePage.java
|       |   |
|       |   |-- utilities
|       |   |   |-- DriverFactory.java
|       |   |   |-- ConfigReader.java
|       |   |
|       |   |-- reports
|       |   |-- api
|       |
|       |-- resources
|           |-- features
|           |   |-- login.feature
|           |
|           |-- testdata
|           |-- config.properties

This structure is simple but clear. It separates feature files from Java code. It separates step definitions from page objects. It gives utilities and reports their own spaces. It also leaves room for API automation if the framework grows. The structure can be expanded by module, but the responsibilities remain the same.

18. Common Folder Structure Mistakes

One major mistake is putting Selenium code inside step definitions. This makes steps long and hard to reuse. Another mistake is storing feature files inside the Java source folder, which mixes behavior documentation with implementation code. Teams also sometimes mix API and UI code in the same package, making it difficult to understand ownership.

Hardcoding test data in step definitions is another common issue. Test data should come from examples tables, test data files, factories, builders, APIs, or configuration sources. Creating very large utility classes is also a problem. A utility class that handles config, screenshots, waits, JSON, Excel, database, and reporting is too broad and should be split.

  • Putting Selenium code inside step definitions.
  • Storing feature files inside Java packages.
  • Mixing API and UI code in the same package without clear separation.
  • Hardcoding test data in step definitions.
  • Creating very large utility God classes.
  • Mixing Page Objects with business logic.
  • Keeping everything in one package.

19. Best Practices

The best practice is to follow the Maven standard directory layout and then add Cucumber-specific packages by responsibility. Keep feature files under `src/test/resources/features`. Keep Java automation code under `src/test/java`. Keep step definitions focused on orchestration. Place Selenium interactions in Page Objects or Screen Objects for mobile automation. Separate UI, API, database, utilities, configuration, reports, and models into dedicated packages.

Store configuration and test data outside Java source files. Organize packages by responsibility, not by convenience. Keep naming consistent across the team. Review folder structure periodically as the framework grows. A structure that worked for 20 scenarios may need refinement when the suite reaches 500 scenarios.

Do not add packages just for decoration. Every package should solve a real organizational problem. A simple framework with clear boundaries is better than a complicated framework with many unused folders.

  • Follow Maven standard directory layout.
  • Keep feature files under `src/test/resources/features`.
  • Keep step definitions thin and focused.
  • Place Selenium interactions in Page Objects.
  • Separate UI, API, database, and utilities.
  • Store configuration and test data outside Java source files.
  • Keep the structure consistent across the team.

20. Interview-Ready Summary

In interviews, a strong answer should explain that a well-structured Cucumber project separates feature files, step definitions, page objects, hooks, utilities, configuration, test data, runners, reports, API clients, database helpers, and models. Feature files belong under `src/test/resources`, while Java automation code belongs under `src/test/java`.

You should also mention that step definitions should remain thin and reusable. They should call page or service classes instead of containing heavy Selenium or API code. Page objects should contain UI locators and actions. Service classes should handle API calls. Utilities should centralize reusable technical helpers. Configuration and test data should be externalized. Following Maven layout makes the framework easier to build, understand, and integrate with CI/CD pipelines.

  • A well-structured Cucumber project separates behavior, orchestration, automation logic, and resources.
  • Feature files belong in `src/test/resources`.
  • Java automation code belongs in `src/test/java`.
  • Thin step definitions and reusable page/service classes improve scalability.
  • Standard Maven structure supports easier builds and CI/CD integration.

21. Golden Rule

The golden rule is to organize your project by responsibility, not by convenience. Feature files describe behavior, step definitions coordinate execution, and page or service classes implement automation logic. Utilities support reusable technical needs. Configuration and test data stay outside hardcoded Java logic. Reports and generated output belong in build folders.

A clean Cucumber JVM project folder structure is not about having many folders. It is about making every file easy to locate, understand, reuse, and maintain. When each folder has a clear reason to exist, the framework becomes easier for the whole team to work with.