skip to content

In Cucumber-JVM, which wins when the same cucumber.* option is set in cucumber.properties and by @ConfigurationParameter?

level: middleimportance: should knowfreq 52%

answer

  1. One vocabulary, several places to write it
  2. The more specific source wins
  3. The properties file is the fallback layer
  4. An annotated value is baked into that suite
  5. Dry run tells you what actually took effect

basics

~20 s

The value closest to the run wins. On a JUnit Platform suite, @ConfigurationParameter is supplied with that suite's own discovery, so it beats a cucumber.properties file on the classpath root; that file is the weakest layer, a project-wide default.

solid answer

~40 s

Cucumber-JVM has one option vocabulary — `cucumber.glue`, `cucumber.filter.tags`, `cucumber.execution.dry-run`, `cucumber.execution.parallel.enabled` — and several places to set it. `@ConfigurationParameter(key = ..., value = ...)` on a JUnit Platform suite class supplies the value as part of that suite's discovery, so it is the most specific source and wins for that suite. A `cucumber.properties` file at the classpath root is the fallback layer, meant for defaults every entry point shares. On the command-line runner the chain is explicit: arguments beat system properties, which beat environment variables, which beat `cucumber.properties`. The practical consequence matters more than the ordering — treat a value hard-coded in the annotation as **fixed for that suite**, and keep anything a pipeline must vary per stage out of it.

code

properties · 4 lines
properties
# cucumber.properties at the test classpath root: project-wide defaults
cucumber.glue=com.vinylmarket.acceptance.steps
cucumber.filter.tags=not @wip
cucumber.execution.dry-run=false

go deeper

for a junior

Know that the same option can be set in more than one place and that a properties file on the test classpath is the weakest of them. Being able to name two places is enough at this level.

for a middle

Explain the layering for both entry points and why the annotation is the specific source on a platform suite. Expect to be asked how you would verify which value actually took effect.

for a senior

Show the operational consequence: what you put in code versus what a pipeline stage supplies, and how you debug a stage that silently keeps running the wrong subset for weeks.

for a principal

Own the convention. Decide which options are structural and belong in code, which are stage-level and belong in job definitions, and how a new team joining the repository discovers that split without reading every suite class.

## One vocabulary, several places to say it Every Cucumber-JVM entry point speaks the same option namespace. `cucumber.glue` names the packages to scan, `cucumber.filter.tags` carries a tag expression, `cucumber.execution.dry-run` walks the run without executing steps, `cucumber.execution.parallel.enabled` switches concurrency on. What changes between entry points is not the *names* but the *places* those names can be written, and therefore which write wins. The places, from most specific to least: - **`@ConfigurationParameter` on a JUnit Platform suite class.** Bound to one suite, supplied as part of that suite's own discovery. It is code: it compiles, it is reviewed, and it moves with the suite. - **The platform's own configuration file at the classpath root.** A shared default for every suite in the module, overridable by a JVM system property. - **JVM system properties**, set with `-D` on the build command. The usual lever for varying a run from CI. - **Environment variables**, useful in containers where nobody controls the command line. - **`cucumber.properties` at the classpath root.** Cucumber's own file, the weakest layer, and the right home for values that are simply true of the project. The rule to say out loud in an interview is short: **the more specific source wins**, and `cucumber.properties` is the least specific thing there is. ## The command-line chain, which is explicit The command-line runner layers its configuration in a fixed order, each layer built on the one below: | Layer | Beats | Typical use | |---|---|---| | Command-line arguments | everything below | one-off and ad-hoc runs | | System properties (`-D`) | env vars and the file | per-stage overrides from CI | | Environment variables | the properties file | container images | | `cucumber.properties` | nothing | project-wide defaults | That ordering is the one people expect, and it is why a `-D` override feels natural. The trap is assuming the same reflex holds on a platform suite, where a value baked into the annotation is not something a command line was ever meant to displace. ## The design consequence, which is the real question Interviewers rarely want the ordering recited; they want to know whether you have been burned by it. The burn looks like this: a team hard-codes `cucumber.filter.tags` into `@ConfigurationParameter` so the pull-request build runs a fast subset, then wants the nightly build to run everything. They add `-Dcucumber.filter.tags=` to the nightly job, nothing changes, and the nightly quietly keeps running the fast subset for weeks. The fixes, in the order you should reach for them: 1. **Do not put varying values in the annotation.** Glue packages and the feature selector belong there — they are structural. Tag filters, parallelism and anything a stage tunes do not. 2. **One suite class per gating tier.** A fast suite and a full suite, each with its own hard-coded filter, is honest and readable, and both are discoverable in code review. 3. **Pass the varying value as a system property** and leave the annotation silent on that key, so the properties file supplies a default and CI supplies the override. 4. **Put genuinely global defaults in `cucumber.properties`** so a new suite class inherits them without repeating four annotations. ## Confirming which value actually took effect Never guess. The cheapest probe is a dry run: `cucumber.execution.dry-run` makes Cucumber walk discovery and step matching without executing any step body, so the scenarios selected and the steps matched tell you exactly which feature selection, tag filter and glue path were in force. A second, blunter probe is to set the key to a deliberately broken value — a glue package that does not exist — and see whether the run notices. If it does not, that source is not the one being read. ## Two files people conflate `cucumber.properties` and the JUnit Platform's own configuration file are different files with different owners. The first holds `cucumber.*` keys and is read on every Cucumber entry point, including the command-line and TestNG routes. The second is the platform's, holds platform keys, and is where a shared default for a platform run belongs. Writing `cucumber.*` keys into the platform file works on a platform suite and does nothing for a command-line run; writing platform keys into `cucumber.properties` does nothing at all. Say which file you mean, every time.

  • Where would you put an option that every Cucumber-JVM suite in the repository should share?
    In `cucumber.properties` at the test classpath root, or in the platform's configuration file if the option only matters for platform runs. Both are weak layers by design, so a suite class or a system property can still override them for one run. Repeating the same `@ConfigurationParameter` on six suite classes is the anti-pattern that answer avoids.
  • Your CI needs a different tag filter per stage. How do you wire that?
    Leave the tag key out of the annotation entirely. Either declare one suite class per tier — a fast suite and a full suite, each explicit in code — or pass the expression as a system property from the job definition with a sane default in `cucumber.properties`. Both keep the varying value outside compiled code.
  • How do you prove which glue path a suite is really using?
    Run it with Cucumber's dry-run option. Discovery and step matching still happen, step bodies do not execute, so undefined steps reveal immediately that the glue package in force is not the one you expected. Comparing that against a deliberately invalid value confirms which source is being read.

saying these in an interview costs you the question

  • Thinks cucumber.properties overrides everything because it is Cucumber's file
  • Assumes -D always wins on a JUnit Platform suite
  • Confuses cucumber.properties with the platform's configuration file
  • Hard-codes the tag filter in the suite class, then edits it per run
  • Believes each entry point has its own separate option names