Cucumber Annotations and Hooks: A Practical Guide
Learn how Java Cucumber step definitions, scenario and step hooks, tag filters, ordering, and scenario-scoped state fit together—with runnable examples.
Direct answer: In Cucumber for Java, annotations connect Gherkin steps and lifecycle methods to Java code. Use @Given, @When, and @Then to bind readable feature steps to methods. Use @Before and @After for scenario-level technical setup and cleanup, and @BeforeStep and @AfterStep only for work that truly needs to run around individual steps. Filter hooks with tag expressions when they should apply only to selected scenarios.
This guide covers Cucumber’s Java API using the current io.cucumber.java package style. Cucumber has implementations in several languages; hook signatures and ordering details can differ. Check the Java API documentation for the Cucumber version in your project before relying on version-sensitive behavior. See the Cucumber Java API source and the Cucumber API reference.
1. How Cucumber annotations work
A step definition is a Java method annotated with a step annotation and an expression. Cucumber loads glue code, matches each feature step’s text to an expression, converts captured values to method parameters, then invokes the matching method. The Gherkin keyword helps people understand the step; matching uses the text after the keyword.
For example, the feature step Given I have 2 items in my basket can match I have {int} items in my basket. The {int} parameter type converts the captured text to an integer.
import io.cucumber.java.en.Given;
public class BasketSteps {
@Given("I have {int} items in my basket")
public void haveItemsInBasket(int count) {
// Establish the scenario's basket state.
}
}
The expression should describe the intended step specifically. Broad or overlapping expressions can cause ambiguous matches. Keep implementation details in Java while keeping the feature text meaningful to its readers.
2. A runnable-shaped Java example
The following feature and glue show how step annotations, a scenario hook, and a readable business precondition fit together. The browser or application interactions are intentionally represented as comments: their implementation depends on the application and automation library. Add the classes to the glue path configured by your Cucumber runner, using the dependency and runner setup for your project’s Cucumber version.
Feature: Basket count
Scenario: A shopper sees the basket count
Given I have 2 items in my basket
When I open the basket
Then I should see 2 items
package example.steps;
import io.cucumber.java.Before;
import io.cucumber.java.Scenario;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
public class BasketSteps {
private int basketCount;
@Before
public void prepareScenario(Scenario scenario) {
// Start low-level test infrastructure, if this scenario needs it.
}
@Given("I have {int} items in my basket")
public void haveItemsInBasket(int count) {
basketCount = count;
// Arrange the application state if this is an integration test.
}
@When("I open the basket")
public void openBasket() {
// Perform the user interaction.
}
@Then("I should see {int} items")
public void shouldSeeItems(int expected) {
if (basketCount != expected) {
throw new AssertionError("Expected " + expected + " items, got " + basketCount);
}
// In a UI test, assert against the visible basket count as well.
}
}
The example demonstrates Java method binding and a plain assertion without assuming a particular browser driver, test runner, or dependency-injection configuration. The Scenario parameter is optional; hooks can accept it when they need scenario information such as its name or status.
3. Step definitions versus hooks
Step definitions implement the behavior described by feature steps. Hooks run at lifecycle boundaries. A hook does not have a Gherkin expression, and adding setup to a hook can hide it from someone who reads only the feature.
| Choice | Scope | Use it for | Trade-off |
|---|---|---|---|
Background or Given |
Feature/scenario steps | Business-relevant preconditions readers should see | More explicit feature text; usually improves specification value |
@Before / @After |
Scenario | Technical setup and cleanup, such as starting infrastructure or releasing resources | Reusable and concise, but hidden from feature readers |
@BeforeStep / @AfterStep |
Individual step | Cross-cutting instrumentation or logging | Fine-grained, but can obscure scenario flow and add overhead |
Cucumber’s API reference advises care with Before: “Whatever happens in a Before hook is invisible to people who only read the features.” Put meaningful business context in a Background or Given step. Reserve hooks for technical work that is not itself part of the behavior under specification.
4. Scenario hooks: setup, cleanup, and tags
Before and After
@Before runs before a scenario’s first step. @After runs after its last step, including outcomes where a step failed, is undefined, pending, or skipped. Use cleanup hooks to release resources even when execution does not reach the end of the scenario normally.
import io.cucumber.java.After;
import io.cucumber.java.Before;
import io.cucumber.java.Scenario;
public class BrowserHooks {
@Before
public void startBrowser() {
// Create browser or other low-level test infrastructure.
}
@After
public void stopBrowser(Scenario scenario) {
if (scenario.isFailed()) {
// Record scenario metadata or diagnostics using your test integration.
}
// Always release resources, including after failures.
}
}
Make cleanup robust: release resources in a way that still works if setup was only partly successful, and avoid allowing one cleanup error to hide the original scenario failure. The exact reporting or attachment API depends on the reporting integration in use.
Restrict hooks with tag expressions
A hook’s source file or class location does not limit which scenarios it applies to. Use a tag expression on the hook to select scenarios. For example, @browser and not @headless selects scenarios tagged @browser unless they also carry @headless.
import io.cucumber.java.Before;
public class BrowserHooks {
@Before("@browser and not @headless")
public void startBrowserForBrowserScenarios() {
// Set up a browser only for matching scenarios.
}
}
Put the tags on scenarios or features according to your suite’s conventions. Tags cannot be attached above a Background or to individual steps. Confirm expression syntax and supported hook options against the Java API version in use.
Ordering hooks
Where several hooks of the same lifecycle type apply, Cucumber Java supports explicit order values, for example @Before(order = 10). Set order when one setup operation depends on another, and document that dependency. Do not rely on incidental source-file or class ordering. After-hook ordering can be inverse to before-hook ordering in some implementations or older materials; consult the current Java API for the exact version before depending on teardown order.
5. Step hooks and run-level hooks
@BeforeStep and @AfterStep run around individual steps. They have invoke-around behavior: when a before-step hook runs, the corresponding after-step hook also runs regardless of that step’s result. Once a step does not pass, later steps and their hooks are skipped. This makes step hooks suitable for cross-cutting instrumentation, but usually a poor home for business behavior.
import io.cucumber.java.AfterStep;
import io.cucumber.java.BeforeStep;
public class StepInstrumentation {
@BeforeStep
public void beforeEachStep() {
// Start a timer or collect cross-cutting diagnostics.
}
@AfterStep
public void afterEachStep() {
// Finish instrumentation for this step.
}
}
@BeforeAll and @AfterAll are run-level hooks called once around the scenario run in the API reference. They are appropriate only for resources whose lifetime should span the run. Their signatures and static requirements depend on the Java API version; use the current reference when adding them.
6. State isolation and dependency injection
Cucumber JVM creates new instances of glue classes before each scenario. Instance fields therefore provide scenario-scoped state by default, which helps prevent one scenario’s state from leaking into another. Avoid mutable static fields for scenario data: they can make scenarios interfere, especially when a runner executes scenarios concurrently.
If several glue classes need the same scenario-scoped collaborator, use a supported dependency-injection module instead of a static singleton. The JVM state guide lists PicoContainer, Spring, Guice, OpenEJB, Weld, Needle, and Quarkus integrations; it recommends PicoContainer when the application does not already use another DI module. DI is not required just to use a glue class with an empty constructor. Follow the current installation instructions for artifact coordinates and runner setup, which vary by project and version.
7. Choosing where setup belongs
- Is the setup part of the business condition? Put it in a readable
GivenorBackground, so the executable specification explains the precondition. - Is it technical infrastructure needed around a scenario? Use
@Beforeand@After. - Does it apply only to a category of scenarios? Add a tag expression to the hook and tag the scenarios.
- Must it run around each step? Use step hooks only for cross-cutting concerns such as instrumentation.
- Do multiple glue classes share state? Use scenario-scoped dependency injection rather than mutable static state.
Given establishes a known state, When describes an event or interaction, and Then states an expected outcome. Keep scenarios focused: extra steps that do not clarify the behavior make the specification harder to read.
8. Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Undefined step | No loaded glue expression matches the step text, or the glue package is outside the runner’s configured path. | Check the exact text after the Gherkin keyword, annotation expression, and glue configuration. Keep package names and runner setup aligned. |
| Ambiguous step definitions | More than one expression matches the same step text. | Narrow or rewrite expressions so each step has one intended match. |
| Parameter conversion or method signature error | The capture or parameter type does not match the Java method parameter. | Use a compatible built-in parameter type such as {int}, or define and register an appropriate parameter type using the current Java API. |
| A hook runs for unexpected scenarios | Hook scope was assumed to follow the source file’s location. | Add a tag expression and apply the tags to the intended scenarios. |
| Business setup is missing from the feature | Meaningful preconditions were placed only in a @Before hook. |
Move the business context into a Given step or Background; leave technical setup in the hook. |
| State leaks between scenarios | Mutable static state or an incorrectly shared collaborator is carrying values between scenarios. | Use instance fields for scenario-local state and scenario-scoped DI for collaborators shared across glue classes. |
| Later steps do not run after failure | Cucumber skips subsequent steps and their step hooks after a non-passing step. | Keep cleanup in scenario-level @After hooks, which run after the scenario outcome, and make teardown safe after partial setup. |
| Teardown order differs from expectation | Code assumes hook ordering that differs across versions or implementations. | Use explicit order where supported and verify Java’s current after-hook behavior for the project’s version. |
9. Performance, reliability, and cost
Hooks add work to every scenario or step in their scope. Keep scenario hooks limited to required setup and teardown, and avoid expensive repeated work in step hooks. Tag filters can prevent browser or service setup from running for scenarios that do not need it. Run-level hooks can suit truly shared resources, but they change resource lifetime and can reduce isolation if shared mutable state is introduced.
For reliable suites, make cleanup safe after partial initialization, keep per-scenario state isolated, and use explicit hook ordering only when there is a real dependency. No Cucumber-specific pricing or performance benchmark is implied here; execution cost depends on the test infrastructure and work performed by the glue.
10. Or skip the browser setup
If your Cucumber suite needs website screenshots for visual checks or diagnostics, ScreenshotNeo provides a website screenshot API and MCP server. One GET request can return a PNG, JPEG, WebP, or PDF. Follow the ScreenshotNeo API documentation for authentication and options.
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
Cookie banners are accepted like a visitor and more than 60 known consent platforms, newsletter popups, and chat widgets are removed before the shot; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify the page verdict and billing status. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up free for 1,000 screenshots a month, with no card required.
11. FAQ
Does the Gherkin keyword form part of the Java expression?
No. The expression matches the step text after the keyword. A Given and a When can therefore match the same expression if their trailing text is identical; avoid relying on keyword differences to disambiguate glue.
Can a hook accept a Scenario argument?
Yes. A scenario hook can accept io.cucumber.java.Scenario when it needs scenario details, such as status. Omit it when the hook does not need that context.
Do I need dependency injection to use Cucumber Java?
No. Glue classes with empty constructors can be used without a DI module. Add one when glue classes need shared scenario-scoped collaborators.
Should every scenario start a browser in a hook?
Only scenarios that need the browser should incur that setup. Use tags to scope browser hooks, and keep business preconditions visible in feature steps.


