Tag Expressions in Cucumber JVM

What Are Tag Expressions?

Tag expressions are logical filters used by Cucumber to decide which features, scenarios, scenario outlines, or examples should be executed based on their tags. Instead of running the entire test suite every time, tag expressions allow the team to run only scenarios that match specific conditions. They are one of the most important execution-control features in Cucumber JVM.

In simple terms, tag expressions are logical rules that determine which scenarios should run. A simple expression such as @Smoke runs smoke scenarios. A more specific expression such as @Smoke and @UI runs only scenarios that have both tags. An exclusion expression such as not @API runs all scenarios except API scenarios. These expressions make Cucumber execution flexible and practical for real automation projects.

Tag expressions are commonly used in Cucumber runner classes, JUnit Platform configuration, Maven or Gradle command-line execution, tagged hooks, and CI/CD pipelines such as Jenkins, GitHub Actions, Azure DevOps, and GitLab CI. They allow the same set of feature files to support many execution modes without duplicating scenarios or creating many separate test suites manually.

A strong understanding of tag expressions is essential for Cucumber JVM because most mature projects rely on them daily. Smoke execution, regression execution, API-only execution, UI-only execution, module-specific execution, environment-specific execution, and exclusion of unstable tests are all usually controlled through tag expressions.

Why Tag Expressions Are Needed

Large Cucumber projects may contain hundreds or thousands of scenarios. These scenarios may belong to different execution groups, layers, modules, priorities, teams, and environments. Running everything every time is often too slow and unnecessary. Tag expressions make it possible to select the right subset for the right situation.

Imagine a project with two thousand scenarios. Some are smoke tests, some are regression tests, some are API scenarios, some are UI scenarios, some belong to customer management, some belong to payment, and some are critical release checks. Without tag expressions, running only API smoke tests would be difficult. Excluding slow or unstable scenarios would be difficult. Running only payment regression would require manual selection or many custom runners.

With tag expressions, the team can write precise filters. @Smoke and @API runs API smoke tests. @Regression and @Payment runs payment regression tests. @Regression and not @Flaky runs regression while excluding known flaky scenarios. (@Smoke or @Critical) and @UI runs UI scenarios that are either smoke or critical.

This flexibility is one reason tags are so powerful. Tags classify scenarios. Tag expressions turn that classification into controlled execution. Together, they allow a single Cucumber suite to support local debugging, CI pipelines, release validation, environment-specific checks, and team-specific runs.

Types of Tag Expressions

Cucumber tag expressions use three main logical operators: and, or, and not. These operators can also be grouped with parentheses to create more precise rules. The syntax is intentionally readable, which makes tag expressions easier to understand than older comma-based or tilde-based filtering styles used in older Cucumber versions.

The and operator means all conditions must be true. The or operator means at least one condition must be true. The not operator excludes matching scenarios. Parentheses control grouping and remove ambiguity when expressions combine multiple operators.

@Smoke
@Smoke and @UI
@Smoke or @Regression
not @API
(@Smoke or @Regression) and not @API

These expressions may look small, but they can control very large execution suites. A CI job may run thousands of scenarios based on one expression. That is why tag names and expressions must be written carefully and consistently.

Basic Syntax

The simplest tag expression is a single tag. If the expression is @Smoke, Cucumber runs scenarios that have the @Smoke tag, either directly or through feature-level or examples-level inheritance.

@Smoke

More complex expressions combine tags with operators. Operators are written as words: and, or, and not. Parentheses can be used to group conditions:

@Smoke and @UI
@Smoke or @Regression
@Regression and not @WIP
(@Smoke or @Critical) and @UI

Readable spacing is recommended. While some tooling may tolerate compact expressions, writing spaces around operators makes expressions easier to scan in runner classes, build scripts, and CI job configuration.

AND Expression

The and operator requires a scenario to contain both tags. If the expression is @Smoke and @UI, Cucumber executes only scenarios that have both @Smoke and @UI. A scenario with only @Smoke does not run. A scenario with only @UI does not run. Both conditions must be true.

@Smoke and @UI

This expression is useful when the team wants a very specific subset. For example, a test suite may contain API smoke tests and UI smoke tests. Running @Smoke executes both. Running @Smoke and @UI executes only UI smoke tests.

The and operator is commonly used for combining execution group, layer, module, environment, and priority tags. Examples include @Regression and @Payment, @Smoke and @QA, @Critical and @API, and @Regression and @Checkout.

AND Example

Consider three scenarios with different tag combinations:

@Smoke @UI
Scenario: Login

@Smoke
Scenario: Search

@UI
Scenario: Dashboard

If the expression is @Smoke and @UI, only the login scenario runs. Search has @Smoke but not @UI. Dashboard has @UI but not @Smoke. Login has both tags, so it satisfies the expression.

This is the key point: and narrows execution. The more tags you combine with and, the smaller and more specific the selected set becomes. This is useful for targeted runs, but it can also accidentally exclude scenarios if tags are missing or inconsistent.

OR Expression

The or operator requires at least one condition to be true. If the expression is @Smoke or @Regression, Cucumber executes scenarios that have either @Smoke, @Regression, or both. A scenario with neither tag is skipped.

@Smoke or @Regression

The or operator expands execution. It is useful when the team wants to include multiple groups in one run. For example, a release validation job may run @Smoke or @Critical to include both smoke checks and critical checks. A module team may run @Payment or @Checkout if a change affects both areas.

Because or expands the suite, it should be used carefully. If too many broad tags are combined with or, the selected set may become much larger than expected. In CI pipelines, this can increase execution time significantly.

OR Example

Consider the following scenarios:

@Smoke
Scenario: Login

@Regression
Scenario: Checkout

@API
Scenario: Create user

If the expression is @Smoke or @Regression, Login and Checkout run. Create user does not run because it has only @API. The expression does not require both tags. It requires at least one of them.

This is useful when two categories are acceptable for the same run. However, if the team intended to run only scenarios that are both smoke and regression, the correct expression would be @Smoke and @Regression, not @Smoke or @Regression. Confusing and and or is a common source of unexpected execution.

NOT Expression

The not operator excludes scenarios with a matching tag. If the expression is not @API, Cucumber executes all scenarios except those tagged with @API. This is useful when the team wants to exclude a category from a larger run.

not @API

Common exclusion tags include @WIP, @Flaky, @Quarantined, @Slow, @Manual, and environment-specific tags that should not run in a given pipeline. For example, a regression pipeline may use @Regression and not @Quarantined to run stable regression scenarios while excluding tests under investigation.

The not operator should not become a way to hide broken tests forever. Excluding scenarios can be practical, but excluded scenarios need ownership and review. Otherwise, important tests may disappear from regular execution.

NOT Example

Consider three scenarios:

@Smoke
Scenario: Login

@API
Scenario: Create customer

@UI
Scenario: Checkout

If the expression is not @API, Login and Checkout run. Create customer is skipped because it has @API. The expression is broad because it includes everything except the excluded tag.

Broad negative expressions should be used carefully. not @API may include UI, database, manual, slow, or environment-specific scenarios unless other filters are added. In many cases, a safer expression is more specific, such as @Regression and not @API or @Smoke and @UI and not @Flaky.

AND with NOT

Combining and with not is very common. It allows the team to select a group and exclude a subset. For example, @Smoke and not @API means the scenario must be smoke and must not be API.

@Smoke and not @API

This expression is useful when smoke contains both UI and API checks, but a particular run should exclude API scenarios. Another common example is @Regression and not @Flaky, which runs regression while excluding known flaky scenarios.

The expression should be read from left to right: include smoke scenarios, but remove scenarios tagged API. This mental model helps avoid mistakes. Positive selection defines the main suite. Negative selection removes exceptions.

OR with NOT

or can also be combined with not, but parentheses often become important. For example, (@Smoke or @Regression) and not @API means run scenarios that are smoke or regression, but exclude API scenarios from that selected set.

(@Smoke or @Regression) and not @API

This expression is different from @Smoke or @Regression and not @API, which can be harder to read and may not express the intended grouping clearly. Parentheses remove ambiguity and make the expression more maintainable.

When combining or and not, always ask what should be included first and what should be excluded second. If the expression is hard to explain in one sentence, it may need parentheses or simplification.

Using Parentheses

Parentheses group parts of a tag expression. They make complex expressions easier to understand and prevent unintended operator precedence problems. Even when an expression would work without parentheses, adding them can make the intent clearer for future maintainers.

(@Smoke or @Critical) and @UI
(@Regression and @Payment) or (@Regression and @Checkout)
(@Smoke or @Regression) and not @WIP

The first expression runs UI scenarios that are either smoke or critical. The second runs payment regression or checkout regression. The third runs smoke or regression scenarios, excluding work-in-progress tests.

Parentheses are especially useful in CI configuration because pipeline expressions may be edited by different people over time. A clear expression reduces the chance that someone changes it incorrectly.

Tag Expressions in CucumberOptions

In Cucumber JVM runner classes, tag expressions can be configured with @CucumberOptions. This is common in projects using JUnit 4 style runners or similar setups.

@CucumberOptions(
    features = "src/test/resources/features",
    glue = "stepdefinitions",
    tags = "@Smoke and @UI"
)
public class TestRunner {
}

This runner executes scenarios that have both @Smoke and @UI. A regression runner might use @Regression and not @WIP. A module runner might use @Regression and @Payment.

Hardcoding expressions in runner classes is simple, but it can become limiting if the team needs many execution combinations. In mature frameworks, tag expressions are often passed from Maven, Gradle, or CI variables so the same runner can support many runs.

Tag Expressions in Maven

Maven execution commonly passes tag expressions through system properties. In modern Cucumber JVM versions, cucumber.filter.tags is commonly used.

mvn test -Dcucumber.filter.tags="@Smoke"

More specific examples include:

mvn test -Dcucumber.filter.tags="@Smoke and @UI"
mvn test -Dcucumber.filter.tags="@Regression and not @Flaky"
mvn test -Dcucumber.filter.tags="(@Smoke or @Critical) and @QA"

This style is flexible and CI-friendly. The same codebase and runner can execute different suites depending on the command. It also keeps tag selection visible in build logs, which helps debugging.

When using command-line expressions, be careful with quoting. Shells and CI tools may interpret parentheses or spaces differently. Put expressions in quotes and test them in the target environment.

Tag Expressions in Gradle

Gradle projects can also pass tag expressions through system properties or test task configuration. The exact setup depends on the project's Gradle build file, but the principle is the same: Cucumber receives a tag expression and filters scenarios accordingly.

gradle test -Dcucumber.filter.tags="@Regression and @API"

Some teams define Gradle tasks such as smokeTest, regressionTest, or apiRegression that set tag expressions internally. This can make commands easier for team members, while still relying on the same underlying tag expression rules.

Whether using Maven or Gradle, keep expressions documented. A short framework guide should list standard commands for smoke, regression, API, UI, module-specific, and environment-specific execution.

Tag Expressions in CI/CD Pipelines

CI/CD pipelines use tag expressions to run the right tests at the right stage. A pull request pipeline may run @Smoke and not @Flaky. A nightly pipeline may run @Regression. A deployment pipeline may run @Smoke and @Staging. A module pipeline may run @Regression and @Payment.

This makes pipelines faster and more focused. Instead of running every scenario for every event, the pipeline can choose a suite based on risk, environment, and timing. Tag expressions are the control surface for this selection.

CI expressions should be treated as production configuration for the test framework. If someone changes @Smoke and @UI to @Smoke or @UI, the pipeline may suddenly run far more scenarios. If someone adds not @API incorrectly, important scenarios may be skipped. Changes to CI tag expressions should be reviewed just like code changes.

Tag Expressions in Tagged Hooks

Cucumber hooks can use tag expressions so setup or teardown logic runs only for matching scenarios. For example, a browser setup hook can run only for UI scenarios.

@Before("@UI")
public void startBrowser() {
    DriverFactory.initializeDriver();
}

Hooks can also use more specific expressions:

@Before("@UI and not @NoBrowser")
public void startBrowser() {
    DriverFactory.initializeDriver();
}

This means the browser starts for UI scenarios unless the scenario is explicitly tagged @NoBrowser. Tagged hooks are useful for technical setup, but they should not hide business behavior. A tag expression in a hook should control framework lifecycle, not secretly perform login, checkout, payment, or assertions.

Common Real-Time Expressions

Several tag expressions appear frequently in real projects. @Smoke runs the smoke suite. @Regression runs regression. @Smoke and @UI runs UI smoke tests. @Regression and @API runs API regression. @Regression and @Payment runs payment regression. @Regression and not @Quarantined runs stable regression by excluding quarantined tests.

Environment-specific expressions are also common: @Smoke and @QA, @Regression and @UAT, and @Smoke and @Staging. Priority-based expressions include @Critical and @UI or (@Smoke or @Critical) and not @Flaky.

These expressions work best when tag naming is consistent. If some scenarios use @smoke and others use @Smoke, the expression will not select the intended full set. Tag expression reliability depends on tag naming discipline.

Expression Design Strategy

Good tag expressions should be clear, stable, and tied to real workflows. Avoid writing clever expressions that only one person understands. A pipeline expression should communicate intent immediately. For example, @Regression and @Checkout and not @Flaky is clear. A long expression with many nested conditions may be harder to maintain.

Start with the main execution group, then narrow by module, layer, environment, or priority, and finally exclude special statuses. A common structure is: include suite, include scope, exclude unstable or irrelevant categories. For example, @Regression and @Payment and not @Quarantined.

When expressions become too complex, review the tag strategy. The suite may need better tag categories or separate pipeline jobs. Complex expressions can be a sign that tags are doing too many things or that scenarios are not organized clearly.

Operator Precedence and Readability

In real teams, the biggest risk with tag expressions is not usually syntax. It is misunderstanding. A developer may read an expression one way, while a tester expects another result. This is especially common when and and or are mixed without parentheses. Even if Cucumber can evaluate the expression correctly, the expression may still be hard for humans to review.

For that reason, parentheses should be used whenever an expression combines different operators. Compare @Smoke or @Critical and @UI with (@Smoke or @Critical) and @UI. The second expression is clearer because it says exactly what the team wants: select scenarios that are smoke or critical, and then require UI. The first expression forces the reader to think about precedence and may lead to mistakes during maintenance.

Readable expressions are easier to debug in CI. When a pipeline unexpectedly runs too many or too few scenarios, a clear expression helps the team identify the issue quickly. A confusing expression creates doubt about whether the problem is tag placement, tag spelling, operator logic, shell quoting, or pipeline configuration.

A useful standard is to require parentheses for any expression that uses both and and or. This may feel slightly verbose, but it improves long-term maintainability. Tag expressions often live in CI configuration where multiple people edit them over time. Explicit grouping protects the suite from accidental meaning changes.

Migrating from Older Tag Syntax

Older Cucumber projects may use older tag filtering styles, such as comma-separated tags or tilde-based exclusions. Modern Cucumber tag expressions use readable logical operators such as and, or, and not. When maintaining legacy frameworks, teams may need to migrate old expressions into the newer format.

The migration should be done carefully because a small expression change can alter which scenarios run. Start by documenting what the old expression is supposed to do. Does it include all smoke tests? Does it exclude work-in-progress scenarios? Does it require both UI and regression tags? Once the intent is clear, rewrite it using modern expressions and run a comparison to confirm that the selected scenarios match expectations.

For example, an old exclusion style that meant "run smoke but exclude WIP" should become @Smoke and not @WIP. A legacy expression that selected multiple categories should become a clear or expression, such as @Smoke or @Critical. If the old syntax had mixed inclusion and exclusion, parentheses may be needed to preserve the intended logic.

Migration is also a good time to clean up tag names. If old expressions include inconsistent tags such as @smoke, @SmokeTest, and @SMOKE, standardize those tags before or during migration. Modern expressions cannot fix poor tag vocabulary. They only filter what exists.

Governance for CI Tag Expressions

CI tag expressions should be governed because they define what evidence a pipeline produces. A smoke pipeline that accidentally runs the wrong expression may give false confidence. A regression pipeline that excludes too much may miss serious failures. A staging pipeline that includes unsafe scenarios may create environment risk.

For important pipelines, tag expressions should be stored in version-controlled configuration, reviewed through pull requests, and documented in the framework guide. Pipeline owners should know why each expression exists and what scenario group it is expected to select. This is especially important for release-blocking jobs.

A good governance habit is to name CI jobs according to their tag expression intent. A job called "UI Smoke QA" should run something like @Smoke and @UI and @QA. A job called "Payment Regression" should run something like @Regression and @Payment. If the job name and expression do not align, reviewers should treat that as a configuration issue.

Teams should also avoid silently expanding or shrinking release suites. Changing @Smoke and @UI to @Smoke or @UI may turn a fast smoke job into a much larger run. Adding not @Slow may remove important coverage. These changes may be valid, but they should be intentional and reviewed.

Auditing Tag Expression Coverage

A tag expression audit checks whether standard expressions select the scenarios the team expects. This is useful when suites grow, tags are renamed, or pipelines are changed. The audit should answer questions such as: how many scenarios run for @Smoke? How many run for @Regression? Which scenarios are excluded by not @Quarantined? Which critical scenarios are not part of release validation?

The audit should compare expression results with business expectations. If the smoke expression selects too many scenarios, the smoke suite may be overloaded. If it selects too few, critical checks may be missing. If @Regression and @Payment returns no scenarios, either payment coverage is missing or tags are inconsistent.

Audits are also useful for status tags. If many scenarios are excluded by @Flaky or @Quarantined, the team should review whether those exclusions are still justified. Exclusion tags are operationally useful, but they can hide risk if they grow unchecked.

A simple report listing tag expression counts can be enough. The team does not need a complex tool to start. Even a periodic review of standard suite sizes can reveal drift early. If smoke had 80 scenarios last month and suddenly has 180, someone should understand why.

Operator Meaning in Practice

A simple way to remember operators is this: and narrows, or expands, and not excludes. If you use and, fewer scenarios usually run because each selected scenario must satisfy more conditions. If you use or, more scenarios usually run because multiple groups are included. If you use not, matching scenarios are removed from the selected set.

This mental model helps when debugging unexpected execution. If too many scenarios are running, look for broad or conditions. If too few scenarios are running, look for missing tags or overly strict and conditions. If important scenarios are skipped, check whether a not condition excludes them.

Debugging Tag Expression Results

When a tag expression does not run the expected scenarios, debug systematically. First, check the exact tag spelling in the feature file. Tags are exact labels, so @Smoke and @smoke are not the same. Next, check whether the tag is applied at the correct level: feature, scenario, scenario outline, or examples block.

Then check the expression logic. If the expression uses and, does the scenario have all required tags? If it uses or, does the scenario have at least one included tag? If it uses not, is the scenario being excluded accidentally? If parentheses are missing, is the grouping different from what the team intended?

Finally, check how the expression is passed to the runner. Command-line quoting, CI variables, shell escaping, and build tool configuration can all affect execution. The expression shown in the build log should match the expression the team intended to run.

Common Mistakes

Confusing AND and OR

@Smoke and @UI runs only scenarios with both tags. @Smoke or @UI runs scenarios with either tag. Mixing these up can dramatically change the selected suite.

Missing Parentheses

Complex expressions are easier to understand with parentheses. Without grouping, readers may misunderstand the intended logic, especially when and, or, and not appear together.

Using Inconsistent Tag Names

Expressions depend on exact tag names. @Regression, @regression, and @RegressionTest are different tags. Inconsistent naming causes missed execution.

Overusing NOT

Excluding tags can be useful, but too many exclusions make expressions hard to understand. If many scenarios must be excluded, the suite may need better tagging or cleanup.

Hiding Broken Tests

Expressions such as not @Flaky are practical only when flaky tests have owners and a fix plan. Exclusion should not become permanent avoidance.

Best Practices

Use simple and readable tag expressions whenever possible. Start with a clear main suite tag such as @Smoke or @Regression, then narrow with module, layer, priority, or environment tags. Use not for temporary or intentional exclusions, and review excluded scenarios regularly.

Use parentheses for mixed expressions. Write (@Smoke or @Critical) and @UI instead of relying on readers to infer precedence. Keep CI tag expressions documented. Review pipeline expression changes carefully because they directly affect test coverage.

Keep tag names consistent. A tag expression is only as reliable as the tags in the feature files. Maintain a tag dictionary, enforce naming conventions in code review, and remove stale duplicate tags. Avoid building overly complex expressions to compensate for poor tag organization.

Real-Time Framework Example

Consider an e-commerce Cucumber JVM suite with tags for execution type, layer, module, environment, and status. The project may use @Smoke, @Regression, @UI, @API, @Checkout, @Payment, @QA, @Staging, and @Quarantined.

A developer changing payment API logic may run:

mvn test -Dcucumber.filter.tags="@Regression and @API and @Payment"

A QA smoke pipeline may run:

mvn test -Dcucumber.filter.tags="@Smoke and @QA"

A release pipeline may run:

mvn test -Dcucumber.filter.tags="(@Smoke or @Critical) and @Staging and not @Quarantined"

Each expression has a clear purpose. The first targets payment API regression. The second validates QA smoke. The third validates staging release-critical checks while excluding quarantined scenarios. This is how tag expressions help one suite serve multiple workflows.

Interview-Ready Explanation

Tag expressions in Cucumber JVM are logical filters used to execute scenarios based on tags. They support operators such as and, or, and not. The and operator requires all tags, or requires at least one tag, and not excludes matching scenarios. Parentheses are used to group conditions in complex expressions.

Tag expressions are used in runner classes, Maven or Gradle commands, tagged hooks, and CI/CD pipelines. Examples include @Smoke and @UI, @Regression and not @Flaky, and (@Smoke or @Critical) and @Staging. Good tag expressions are readable, documented, and based on consistent tag naming.

Summary

Tag expressions are the execution logic behind Cucumber tags. Tags classify scenarios, while tag expressions decide which classified scenarios run. They allow teams to execute smoke, regression, API, UI, module, priority, and environment-specific suites without duplicating feature files.

The golden rule is to keep expressions simple, precise, and readable. Use and to narrow, or to expand, not to exclude, and parentheses to make grouping clear. When tag expressions are designed well, Cucumber JVM execution becomes flexible, predictable, and suitable for real CI/CD workflows.