Resource library

QA How-To

Java for Testers: Cucumber hooks and tags (2026)

Master java testers Cucumber hooks and tags with conditional setup, hook order, tag expressions, JUnit Platform configuration, and reliable cleanup in CI.

26 min read | 2,693 words

TL;DR

Cucumber hooks run automation setup or teardown around scenarios and steps, while tags classify scenarios and select conditional hooks or execution subsets. Use `@Before`, `@After`, `@BeforeStep`, and `@AfterStep` sparingly, filter them with expressions such as `@ui and not @headless`, and keep cleanup idempotent.

Key Takeaways

  • Use hooks for low-level automation plumbing, while keeping business-visible preconditions in Gherkin steps or Backgrounds.
  • Filter hooks with boolean tag expressions so expensive browser, database, and API setup runs only where required.
  • Design every After hook to tolerate failed or partially completed setup because cleanup still runs after failed scenarios.
  • Keep the tag vocabulary small and purpose-based, separating capability, execution, risk, and lifecycle metadata.
  • Treat hook order as an explicit dependency only when composition cannot remove that dependency, then lock it with tests.
  • Configure scenario selection through `cucumber.filter.tags` and remember that name and tag filters combine with AND.
  • Never store scenario state in static fields, especially when Cucumber scenarios run in parallel.

The java testers Cucumber hooks and tags topic is about controlling automation lifecycle without turning feature files into framework configuration. Hooks run setup, teardown, or evidence capture around scenarios and steps. Tags classify Gherkin elements, select a run subset, and restrict a hook to matching scenarios.

The mechanics are simple, but poor lifecycle design creates invisible setup, order coupling, skip cascades, and parallel failures. This guide uses current Cucumber-JVM 7.34.4 APIs, Java 17, and the JUnit Platform engine. It shows correct hook signatures, boolean tag expressions, configuration options, failure attachments, tag governance, and interview answers.

TL;DR

Requirement Cucumber mechanism Example
Setup before each scenario @Before Start a scenario-scoped browser
Cleanup after each scenario @After Close browser even after failure
Setup only for UI scenarios Conditional hook @Before("@ui")
Capture a failed screenshot Scenario.isFailed() and attach Attach PNG bytes in @After
Select a CI subset Tag filter @smoke and not @quarantine
Run logic around steps @BeforeStep, @AfterStep Lightweight step diagnostics
One operation for the full run Static @BeforeAll, @AfterAll Validate immutable configuration

Keep business facts visible. A Background that says a customer has an active subscription is readable to stakeholders. A hook that creates an unseen subscription is not.

1. What java testers Cucumber hooks and tags Control

Cucumber-JVM creates glue objects and executes matched step definitions for each scenario. Hooks are glue methods that participate at defined lifecycle points. Scenario hooks run before the first step or after the last step. Step hooks run before or after each executed step. Global hooks run once before or after the entire Cucumber run.

Tags are labels beginning with @, such as @smoke, @api, or @destructive. They can appear above Feature, Rule, Scenario, Scenario Outline, and Examples. A child inherits tags from its parents. Tags cannot be attached to a Background or individual Given, When, or Then step.

These features intersect in two ways. A tag filter controls which scenarios enter a run, while a conditional hook expression controls whether a hook runs for an included scenario. For example, the CI command may select @smoke and not @quarantine; within that subset, a hook annotated @Before("@ui") starts a browser only for UI scenarios.

Do not confuse tags with assertions. A scenario tagged @critical does not become more reliable or receive a timeout automatically unless your configuration or glue gives that tag a defined behavior. Every tag should have a documented purpose and owner. Every hook should have one lifecycle responsibility and work with scenario-scoped dependencies.

2. Current Cucumber-JVM Setup with JUnit Platform

Cucumber-JVM 7.34.4 requires a modern Java baseline and its current line uses Java 17. Keep all Cucumber artifacts on the same version to avoid message, engine, and glue incompatibilities. The following Maven setup uses the Cucumber Java module, JUnit Platform engine, PicoContainer integration, and the JUnit Platform suite artifact.

<properties>
  <maven.compiler.release>17</maven.compiler.release>
  <cucumber.version>7.34.4</cucumber.version>
  <junit.platform.version>6.0.2</junit.platform.version>
</properties>

<dependencies>
  <dependency>
    <groupId>io.cucumber</groupId>
    <artifactId>cucumber-java</artifactId>
    <version>${cucumber.version}</version>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>io.cucumber</groupId>
    <artifactId>cucumber-junit-platform-engine</artifactId>
    <version>${cucumber.version}</version>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>io.cucumber</groupId>
    <artifactId>cucumber-picocontainer</artifactId>
    <version>${cucumber.version}</version>
    <scope>test</scope>
  </dependency>
  <dependency>
    <groupId>org.junit.platform</groupId>
    <artifactId>junit-platform-suite</artifactId>
    <version>${junit.platform.version}</version>
    <scope>test</scope>
  </dependency>
</dependencies>

A suite class selects the engine and classpath feature location. The glue and plugins can live in src/test/resources/junit-platform.properties, which avoids a dense annotation list.

package example.suite;

import org.junit.platform.suite.api.IncludeEngines;
import org.junit.platform.suite.api.SelectClasspathResource;
import org.junit.platform.suite.api.Suite;

@Suite
@IncludeEngines("cucumber")
@SelectClasspathResource("features")
public class RunCucumberTest {
}
cucumber.glue=example.steps
cucumber.plugin=pretty,html:target/cucumber-report.html,json:target/cucumber.json
cucumber.publish.quiet=true

Run it with mvn test. Confirm discovery by temporarily adding a failing scenario before trusting a new pipeline.

3. Scenario Hooks and Reliable Hook Order

Annotated Java hooks come from io.cucumber.java. A @Before method runs before the scenario's first step. An @After method runs after its last step even when the scenario is failed, undefined, pending, or skipped. That guarantee makes @After the right place for cleanup, but cleanup must tolerate a setup method that failed halfway.

package example.steps;

import io.cucumber.java.After;
import io.cucumber.java.Before;

public final class LifecycleHooks {
    private final ScenarioResources resources;

    public LifecycleHooks(ScenarioResources resources) {
        this.resources = resources;
    }

    @Before(order = 10)
    public void createScenarioWorkspace() {
        resources.createWorkspace();
    }

    @Before(value = "@ui", order = 20)
    public void startBrowser() {
        resources.startBrowser();
    }

    @After(value = "@ui", order = 20)
    public void stopBrowser() {
        resources.stopBrowserIfStarted();
    }

    @After(order = 10)
    public void removeScenarioWorkspace() {
        resources.removeWorkspaceIfCreated();
    }
}

For @Before, lower order values run first. For @After, higher order values run first, which supports stack-like unwinding. In the example, the workspace exists before the browser starts, then the browser stops before the workspace is removed. Use spaced values such as 10 and 20 so a later hook can be inserted without renumbering every method.

Order is still a dependency. Prefer one orchestrating hook or a cohesive resource object when two operations must always move together. If multiple hook classes rely on order, add a focused lifecycle test and document the dependency. File location and class name do not determine hook order.

4. Conditional Hooks with Cucumber Tag Expressions

A conditional hook takes a tag expression in the annotation value. Expressions support and, or, not, and parentheses. Use lowercase operators and parentheses whenever mixing operations so intent is obvious.

@Before("@api and not @read-only")
public void createApiData() {
    resources.createApiData();
}

@Before("@ui and (@chrome or @firefox)")
public void startTaggedBrowser() {
    resources.startBrowser();
}

@After("@destructive or @creates-data")
public void cleanCreatedData() {
    resources.deleteOwnedData();
}

A conditional hook is evaluated against all inherited tags on the scenario. If a feature has @ui, every scenario under it matches @ui unless selection logic excludes the scenario for another reason. Tags on an Examples block apply to cases generated from that block, which is useful for separating mobile and desktop example sets.

Avoid using conditional hooks to encode business preconditions. @premium-customer followed by an invisible database insert makes the scenario shorter but hides meaning. Write Given a premium customer exists when that fact belongs in the behavior narrative. Reserve hooks for technical context such as creating a browser, resetting a mock server, beginning a trace, or cleaning resources.

Also avoid one hook per tag if several tags describe the same technical capability. A capability context can interpret configuration after one well-scoped condition. This reduces overlapping setup and makes tag combinations easier to reason about.

5. Step Hooks, Global Hooks, and Their Tradeoffs

@BeforeStep and @AfterStep run around steps. If a BeforeStep hook runs, the corresponding after-step lifecycle still executes even when the step fails. Later steps and their hooks are skipped after a failing step. Step hooks can receive Scenario, and current Cucumber-JVM releases also expose step information in supported hook APIs, but portable test code should depend only on information it truly needs.

import io.cucumber.java.AfterStep;
import io.cucumber.java.BeforeStep;
import io.cucumber.java.Scenario;

@BeforeStep("@trace")
public void beforeTracedStep(Scenario scenario) {
    resources.trace().mark("before step in " + scenario.getName());
}

@AfterStep("@trace")
public void afterTracedStep() {
    resources.trace().flushCurrentStep();
}

Step hooks multiply quickly. A suite with 500 scenarios and 10 steps can execute a selected step hook thousands of times. Do not take a screenshot after every step by default, poll databases, or send remote telemetry synchronously. Use them for lightweight diagnostics and enable expensive behavior only for a targeted tag or troubleshooting run.

Global @BeforeAll and @AfterAll hooks are static in annotated Java classes. They run once for the Cucumber run, not once per worker or browser unless the launcher structure happens to align. Use them for immutable configuration validation or a process-level report resource. Do not put scenario data, user accounts, or a shared WebDriver in global state. Parallel scenarios would race, and cleanup after a launcher failure may be incomplete.

import io.cucumber.java.AfterAll;
import io.cucumber.java.BeforeAll;

@BeforeAll
public static void validateConfiguration() {
    RequiredSettings.validate(System.getenv());
}

@AfterAll
public static void finishRunReport() {
    RunReport.closeIfOpen();
}

6. Failure Screenshots and Scenario Attachments

The io.cucumber.java.Scenario interface lets a hook inspect status and attach evidence. For a browser test, capture the screenshot before closing the browser. Give the screenshot hook a higher @After order than the close hook so reverse teardown order is explicit.

package example.steps;

import io.cucumber.java.After;
import io.cucumber.java.Scenario;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;

public final class EvidenceHooks {
    private final BrowserSession browser;

    public EvidenceHooks(BrowserSession browser) {
        this.browser = browser;
    }

    @After(value = "@ui", order = 100)
    public void attachScreenshotOnFailure(Scenario scenario) {
        WebDriver driver = browser.driverIfStarted();
        if (scenario.isFailed() && driver instanceof TakesScreenshot camera) {
            byte[] png = camera.getScreenshotAs(OutputType.BYTES);
            scenario.attach(png, "image/png", "failure screenshot");
            scenario.log("URL: " + driver.getCurrentUrl());
        }
    }
}

The Selenium methods shown are current Selenium 4 APIs. BrowserSession is an application fixture that returns the scenario's driver or null, so the hook handles failed initialization. Do not invent a driver when setup failed. Do not let a screenshot exception replace the product failure. Catch only known evidence-capture failures, log them, and retain the original scenario result.

Attachments should be useful and safe. A screenshot may contain personal data, access tokens, or internal URLs. Apply retention and redaction rules, especially in shared CI reports. Attach structured request and response summaries for API scenarios, but exclude authorization headers and secrets. The Allure vs Extent Reports comparison can help teams decide how Cucumber artifacts enter the wider reporting stack.

7. Designing a Tag Taxonomy That Scales

A tag vocabulary should answer operational questions without becoming a second feature language. Separate dimensions so combinations remain meaningful. A compact taxonomy may include capability (@ui, @api), execution (@smoke, @regression), risk (@critical), environment need (@requires-payment-sandbox), and lifecycle (@quarantine, @wip).

Tag dimension Good examples Weak examples Owner question
Test layer @api, @ui, @contract @automation What technical boundary runs?
CI subset @smoke, @regression @run-me Which pipeline selects it?
Business capability @checkout, @refunds @feature1 Which domain owns it?
Environment need @requires-email-sandbox @special-env What resource is required?
Temporary state @quarantine, @wip @ignore-forever Who removes it and when?

Do not encode values into a proliferation of tags such as @browser-chrome-136. Browser choice normally belongs in runtime configuration or a matrix. Tags should state that a scenario requires a browser, while the pipeline selects the browser. Use a specific browser tag only when behavior is intentionally browser-specific.

Keep a tag registry in the repository. For each tag, document definition, allowed Gherkin levels, selecting pipeline, conditional hooks, owner, and expiration policy if temporary. Add a static check for unknown tags and require a reason for quarantine. The Cucumber vs Playwright BDD guide provides a broader framework decision context.

8. Selecting Scenarios in Maven and CI

Cucumber accepts the cucumber.filter.tags configuration property. With Maven, pass it as a JVM system property. Quote the expression so the shell does not interpret spaces or parentheses.

mvn test -Dcucumber.filter.tags="@smoke and not @quarantine"

You can put a stable default in junit-platform.properties, but CI command-line values are better for pipeline-specific selection. Keep the full regression definition in source control rather than assembling an unreadable string across many workflow variables.

cucumber.filter.tags=not @wip and not @manual

Cucumber also supports cucumber.filter.name for scenario-name filtering. Name and tag filters combine with AND, so a scenario must satisfy both. Name filtering is helpful for local diagnosis but brittle as a permanent suite definition because product language changes. CI should primarily use tags, feature paths, and stable suite configuration.

Tag filtering differs from conditional hooks. If the run excludes a scenario, none of its steps or hooks execute. If the run includes it but a hook expression does not match, only that hook is skipped. Log the effective selection expression in CI and publish a discovered-scenario count. A green run with zero selected scenarios is not evidence of quality. Seed a known tagged scenario when validating pipeline changes.

Avoid several competing selection layers unless necessary. Feature path selection, engine discovery, name regex, tag expression, and build-tool include patterns can intersect into an empty suite. Document the final selection path and add a launch smoke check.

9. State, Dependency Injection, and Parallel Execution

Cucumber creates new instances of glue classes for each scenario. If different step and hook classes need the same scenario state, use constructor injection with a supported object factory. PicoContainer is the recommended lightweight option when the application does not already use another dependency-injection module. The cucumber-picocontainer dependency lets Cucumber resolve constructor graphs and share scenario-scoped objects.

public final class ScenarioResources {
    private BrowserSession browser;
    private String createdCustomerId;

    public void startBrowser() {
        browser = BrowserSession.start();
    }

    public boolean browserStarted() {
        return browser != null;
    }

    public void stopBrowserIfStarted() {
        if (browser != null) {
            browser.close();
            browser = null;
        }
    }

    public void rememberCustomer(String id) {
        createdCustomerId = id;
    }
}

A hook class and step class can both request ScenarioResources in their constructors. Do not use public static fields, singleton scenario contexts, or a global driver. Those patterns appear to work sequentially and fail under parallel execution when one scenario overwrites another's state or closes its browser.

Cleanup methods should be idempotent and ownership-aware. stopBrowserIfStarted() can run after a failed start. Data cleanup should delete only identifiers recorded by the current scenario, not truncate a shared table. If the suite needs parallel execution, audit accounts, files, ports, downloads, mock-server mappings, and report writers in addition to Java fields. See dependency injection with PicoContainer for testers for the complete construction pattern.

10. Testing java testers Cucumber hooks and tags

Hooks deserve tests because they are executable framework code. The highest-value check is a tiny feature containing passing, failing, UI, API, and quarantined scenarios. Run it through the same JUnit Platform engine used by CI, then assert emitted events, attachments, created resources, and cleanup outcomes. Avoid testing hook annotations only through reflection because that does not prove engine selection or order.

Create fake scenario resources for unit-level logic. A cleanup method can be tested against never-started, started, already-closed, and partially initialized states. An evidence service can accept a small interface instead of a concrete browser so failure behavior is deterministic. Keep one end-to-end hook test with a real engine to catch glue discovery and tag expression problems.

Review tag filters with truth tables. For @smoke and not (@quarantine or @destructive), list representative tag sets and expected selection. This is faster and clearer than diagnosing a missing CI scenario after release. Static checks can also reject unknown tags, tags on disallowed levels, or quarantine metadata without an owner.

Finally, test empty selection. A pipeline should fail or warn loudly when a suite expected to discover scenarios finds none. Store report artifacts even when hooks fail, and keep cleanup failures visible without overwriting the original assertion. Lifecycle code is part of the test platform and deserves the same review, naming, and regression discipline as page objects and API clients.

Interview Questions and Answers

Q: What is the difference between a Cucumber hook and a Background?

A hook handles low-level technical lifecycle such as browser startup or cleanup and is usually invisible in Gherkin. A Background states a business-readable precondition shared by scenarios in a feature or rule. I use Backgrounds for facts stakeholders should understand and hooks for automation plumbing.

Q: Does an After hook run when a scenario fails?

Yes. Scenario @After hooks run after failed, undefined, pending, and skipped outcomes as well as passing scenarios. Cleanup must therefore tolerate partially completed setup.

Q: How does hook order work in Cucumber-JVM?

Before hooks run from lower order values to higher values. After hooks unwind in the opposite direction, with higher order values first. I avoid unnecessary ordering and test any dependency that remains.

Q: How do conditional hooks work?

The annotation contains a tag expression such as @ui and not @headless. Cucumber evaluates the expression against the scenario's own and inherited tags. The hook runs only when the scenario is included and the expression matches.

Q: What is wrong with a static WebDriver in hooks?

It shares mutable scenario state and breaks isolation, especially in parallel runs. One scenario can overwrite or close another scenario's driver. I inject a scenario-scoped browser session instead.

Q: When should step hooks be used?

Use them sparingly for lightweight diagnostics or targeted tracing. They execute around many steps, so screenshots, network calls, and heavy logging can make suites slow and noisy.

Q: How do you run smoke scenarios but exclude quarantine?

I pass -Dcucumber.filter.tags="@smoke and not @quarantine" to Maven or configure the equivalent property. I also publish discovery counts so a bad expression cannot create a misleading empty green run.

Q: How do tags inherit in Gherkin?

Tags on a Feature flow to its Rules, Scenarios, Scenario Outlines, and Examples. Tags on a Scenario Outline also apply to its Examples, while an Examples block can add its own tags. Backgrounds and steps cannot be tagged.

Common Mistakes

  • Hiding business preconditions such as customer status inside @Before hooks.
  • Starting a browser for every scenario instead of restricting setup to @ui.
  • Closing the browser before the screenshot hook has captured failure evidence.
  • Depending on hook class or file order instead of explicit order values.
  • Letting cleanup throw a null pointer when setup failed before allocating a resource.
  • Storing scenario context or WebDriver in static fields.
  • Using overlapping tags with no registry, owner, or defined pipeline behavior.
  • Treating @quarantine as a permanent skip without an expiry and defect link.
  • Taking screenshots after every step in the full regression suite.
  • Combining feature, name, tag, and build filters without checking for zero discovery.
  • Logging tokens, personal data, or complete request headers in attachments.

Conclusion

The java testers Cucumber hooks and tags model is reliable when hooks stay technical, tags stay intentional, and state stays scoped to one scenario. Use conditional expressions to avoid irrelevant setup, capture evidence before teardown, make cleanup idempotent, and verify order through a real engine test.

Start by inventorying every hook and tag in the suite. Remove hidden business setup, document the remaining vocabulary, and run a small lifecycle feature through the same JUnit Platform configuration as CI. That gives the team a trustworthy foundation for readable BDD scenarios and maintainable automation.

Interview Questions and Answers

Explain Cucumber hooks and tags in Java.

Hooks are glue methods for technical lifecycle operations around scenarios, steps, or the full run. Tags classify Gherkin elements and support run filtering or conditional hooks. I keep business preconditions in readable steps and use tags to control only documented operational behavior.

How do you run a hook only for UI scenarios?

I annotate it with a conditional expression such as `@Before("@ui")`. If exclusions matter, I make them explicit, for example `@ui and not @headless`. The condition sees tags inherited from Feature and Rule levels.

What happens to After hooks when a step fails?

Scenario After hooks still run, which lets the framework capture evidence and clean resources. I ensure screenshot capture occurs before driver closure and make every cleanup operation safe after partial setup. Cleanup errors remain visible but must not erase the original failure.

Describe hook ordering in Cucumber-JVM.

Before hooks execute from lower order values to higher values. After hooks execute from higher values to lower values, giving stack-like teardown. I use order only for real lifecycle dependencies and lock the behavior with an engine-level test.

How would you design a Cucumber tag taxonomy?

I separate dimensions such as layer, CI subset, business capability, environment need, and temporary lifecycle state. Each tag gets a definition, owner, selecting pipeline, and any conditional hooks. I reject unknown tags and expire quarantine tags.

Why should a WebDriver not be static in Cucumber hooks?

Static state is shared across scenarios and can be overwritten or closed by another worker. Cucumber scenarios should have isolated browser sessions. I use constructor injection with a scenario-scoped context and idempotent teardown.

How do you attach a screenshot to a failed scenario?

In an After hook, I check `Scenario.isFailed()`, verify that a driver actually started, capture PNG bytes with Selenium's `TakesScreenshot`, and call `scenario.attach(bytes, "image/png", name)`. The hook runs before browser cleanup and does not replace the original failure if capture fails.

How do tag and name filters interact?

Cucumber combines `cucumber.filter.tags` and `cucumber.filter.name` using AND. A scenario must satisfy both filters. I prefer tags for stable CI suites and use name filters mainly for local diagnosis.

Frequently Asked Questions

What are hooks in Cucumber Java?

Hooks are Java glue methods that run before or after scenarios, steps, or the full Cucumber run. They are intended for technical setup, teardown, and diagnostics rather than hidden business behavior.

Can Cucumber hooks use tags?

Yes. Put a tag expression in the hook annotation, such as `@Before("@ui and not @mobile")`. Cucumber evaluates it against the scenario's direct and inherited tags.

What is the order of Before and After hooks?

Before hooks run in ascending order, while After hooks run in descending order. This allows later setup to be torn down first, but unnecessary order dependencies should still be removed.

Does a Cucumber After hook run after a failed scenario?

Yes. It also runs after undefined, pending, or skipped outcomes. Write cleanup so it can handle resources that were never created or were only partially initialized.

How do I filter Cucumber scenarios by tags in Maven?

Pass the `cucumber.filter.tags` system property, for example `mvn test -Dcucumber.filter.tags="@smoke and not @quarantine"`. Quote expressions that contain spaces or parentheses.

Should I use BeforeStep and AfterStep for screenshots?

Usually only in targeted diagnostic runs. Step hooks execute frequently, so unconditional screenshots add time, storage, noise, and potential exposure of sensitive data.

Can tags be placed on a Cucumber Background?

No. Tags can be placed on Feature, Rule, Scenario, Scenario Outline, and Examples elements, but not on Backgrounds or individual steps.

Related Guides