Cucumber is a collection of libraries, not a single download. Getting the right combination of artifacts into your Maven project — and wiring them so your IDE and mvn test both discover .feature files — is where most beginners spend their first frustrating hour. This lesson covers the exact pom.xml setup, project structure, and runner class you need to go from zero to first green scenario.
What you need on the classpath
A Cucumber + Java project needs three groups of dependencies working together:
- Cucumber runtime — parses
.featurefiles, matches steps, runs the test lifecycle - Test runner integration — bridges Cucumber to JUnit 5 or TestNG so
mvn testand your IDE can discover and run scenarios - Automation libraries — Selenium, Rest Assured, or whatever drives the system under test
The Cucumber runtime itself is always cucumber-java. The bridge depends on your runner choice.
Maven dependencies — JUnit Platform (recommended for new projects)
<dependencies>
<!-- Cucumber core runtime — step definition annotations, Gherkin parser -->
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>7.18.0</version>
<scope>test</scope>
</dependency>
<!-- Bridge: registers Cucumber as a JUnit Platform test engine -->
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit-platform-engine</artifactId>
<version>7.18.0</version>
<scope>test</scope>
</dependency>
<!-- JUnit Platform Suite — enables the @Suite runner class -->
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-suite</artifactId>
<version>1.10.2</version>
<scope>test</scope>
</dependency>
<!-- Selenium (for UI BDD in later chapters) -->
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.21.0</version>
<scope>test</scope>
</dependency>
<!-- PicoContainer — DI for sharing state between step definition classes -->
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-picocontainer</artifactId>
<version>7.18.0</version>
<scope>test</scope>
</dependency>
</dependencies>Keep all Cucumber artifacts at the same version. Mixing cucumber-java 7.18.0 with cucumber-junit-platform-engine 7.15.0 will cause subtle classpath conflicts that produce confusing errors. Use a Maven property: <cucumber.version>7.18.0</cucumber.version> and reference it as ${cucumber.version} in every Cucumber artifact.
Alternative: TestNG runner
If your project already uses TestNG (from a Selenium or Rest Assured course), swap the JUnit bridge for cucumber-testng:
<!-- Use instead of cucumber-junit-platform-engine -->
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-testng</artifactId>
<version>7.18.0</version>
<scope>test</scope>
</dependency>The feature files and step definitions are identical. Only the runner class differs.
Maven Surefire plugin
Surefire must be at 3.x (or 2.22.2 minimum) to discover JUnit Platform tests:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>
</plugins>
</build>Without this, mvn test may silently find zero tests and report BUILD SUCCESS with nothing actually run — the classic false positive that wastes hours.
Project structure
Maven enforces src/test/java for test classes and src/test/resources for non-Java resources on the test classpath. Cucumber discovers .feature files from the classpath, so they belong under src/test/resources/:
bdd-project/
├── pom.xml
└── src/test/
├── java/
│ ├── stepdefinitions/ ← @Given/@When/@Then classes
│ ├── pages/ ← Page Object Model classes
│ ├── hooks/ ← @Before/@After classes
│ ├── context/ ← shared state objects (for DI)
│ └── runners/ ← test runner class
└── resources/
├── features/ ← .feature files
│ ├── login.feature
│ └── checkout.feature
└── junit-platform.properties ← JUnit Platform config
This layout is the same package discipline as a Selenium with Java or Rest Assured project — if you've done either of those courses, every folder name is already familiar.
The runner class (JUnit Platform)
The runner is a plain Java class with no methods — just annotations that configure Cucumber:
// src/test/java/runners/RunCucumberTest.java
package runners;
import org.junit.platform.suite.api.*;
import static io.cucumber.junit.platform.engine.Constants.*;
@Suite
@IncludeEngines("cucumber")
@SelectClasspathResource("features")
@ConfigurationParameter(key = GLUE_PROPERTY_NAME, value = "stepdefinitions,hooks")
@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, value = "pretty, html:target/cucumber-reports.html")
public class RunCucumberTest { }Key annotations:
@Suite— marks this as a JUnit Platform suite@IncludeEngines("cucumber")— tells the suite to use the Cucumber engine@SelectClasspathResource("features")— the classpath path to.featurefiles (resolves tosrc/test/resources/features/)@ConfigurationParameter(key = GLUE_PROPERTY_NAME, ...)— the packages where Cucumber scans for step definitions and hooks; multiple packages comma-separated@ConfigurationParameter(key = PLUGIN_PROPERTY_NAME, ...)— output plugins:prettyfor console,html:pathfor an HTML report
The glue package is the most common misconfiguration. If Cucumber can't find your step definitions, check this first.
The runner class (TestNG)
With cucumber-testng, extend AbstractTestNGCucumberTests:
// src/test/java/runners/RunCucumberTest.java
package runners;
import io.cucumber.testng.AbstractTestNGCucumberTests;
import io.cucumber.testng.CucumberOptions;
@CucumberOptions(
features = "src/test/resources/features",
glue = {"stepdefinitions", "hooks"},
plugin = {"pretty", "html:target/cucumber-reports.html"}
)
public class RunCucumberTest extends AbstractTestNGCucumberTests { }@CucumberOptions is the TestNG equivalent of the JUnit Platform @ConfigurationParameter set.
How Cucumber executes a scenario
Step 1 of 7
Maven launches runner
Surefire finds RunCucumberTest; the JUnit Platform (or TestNG) discovers the Cucumber engine.
Running the tests
Three ways you'll use in practice:
- IntelliJ: right-click
RunCucumberTest→ Run. You can also right-click a.featurefile to run a single feature, or right-click a specific scenario line. - Maven CLI:
mvn testruns everything.mvn test -Dcucumber.filter.tags="@smoke"runs only smoke-tagged scenarios. - IDE feature file: click the green play icon next to any scenario. IntelliJ integrates with both JUnit Platform and TestNG Cucumber runners.
⚠️ Common mistakes
- Mismatched Cucumber artifact versions. All
io.cucumberartifacts must share the same version. Pinningcucumber-javaat 7.18.0 andcucumber-junit-platform-engineat 7.15.0 causesClassNotFoundExceptionorIncompatibleClassChangeErrorat runtime. - Wrong glue package. If the glue package in the runner doesn't match where your step definition classes actually live, every step is
Undefined. Start with a single package (e.g.,stepdefinitions) and expand. - Outdated Surefire. Maven's default Surefire version (2.x) predates JUnit Platform support.
mvn testreportsBUILD SUCCESSwith zero tests run. Explicitly pin Surefire to 3.x in<build><plugins>. .featurefiles insrc/test/java/. Placing feature files next to Java source compiles fine in IntelliJ but they may not be on the classpath when Maven runs. Always put them undersrc/test/resources/.
🎯 Practice task
Get a Cucumber project building and a scenario running. 35–45 minutes.
- Create a new Maven project in IntelliJ (GroupId
com.mycompany.bdd, ArtifactIdbdd-framework). - Add the four dependencies from this lesson:
cucumber-java,cucumber-junit-platform-engine,junit-platform-suite, andcucumber-picocontainer. Pin the Surefire plugin at 3.2.5. Click "Load Maven changes". - Create the package structure under
src/test/java/:stepdefinitions,hooks,runners. Createsrc/test/resources/features/. - Create
runners/RunCucumberTest.javawith the JUnit Platform annotations pointing glue atstepdefinitions,hooks. - Create
src/test/resources/features/hello.featurewith one scenario:Given I have Cucumber set up/When I run the tests/Then I should see a green result. Run the runner — Cucumber will reportUndefinedsteps with suggested snippets. - Copy each snippet into a new class
stepdefinitions/HelloSteps.java, implement each withSystem.out.println(...). Run again — all three steps should be green. - Run
mvn testfrom the terminal. ConfirmBUILD SUCCESSand thattarget/cucumber-reports.htmlexists. - Stretch: switch to the TestNG runner. Replace
cucumber-junit-platform-engineandjunit-platform-suitewithcucumber-testng. Update the runner class to extendAbstractTestNGCucumberTests. Confirm the same scenario still runs green under TestNG.
Next lesson: feature files and Gherkin syntax in detail — every keyword you'll use in practice.