Undefined hook execution order across multiple glue packages in Cucumber JVM
0 reputation · 17 Dec 2020, 02:08 UTC
0 reputation · 17 Dec 2020, 02:08 UTC
Cucumber JVM (7.x) scans every glue package for classes containing @Before and @After methods and registers all hooks it finds. The documentation states that execution order is undefined unless an explicit order is given. In a project that keeps scenario setup and teardown logic in several glue packages — for example, one package for database fixtures and another for application state — the sequence in which hooks run is not guaranteed to be stable across builds, after a new package is added, or under parallel execution where each runner may resolve glue classes independently.
The goal is a repeatable development environment in which hook execution is deterministic without forcing every hook class into a single package. Constraints: versions before 7.0 may resolve glue classes through a different scanning mechanism, so any ordering convention must be validated against the version in use; classloader registration order is not a safe basis for sequencing.
@Before and @After hooks that live in different glue packages?35275 reputation · 17 Dec 2020, 03:40 UTC
In Cucumber JVM 7.x the documented mechanism is the order attribute on io.cucumber.java.Before and io.cucumber.java.After — an int that defaults to 1000. Once every glue package has been scanned and all hooks registered, Cucumber sorts them by this value: @Before hooks run in ascending order, @After hooks in descending order. The sort is global across all registered hooks, so where a class lives is irrelevant; setup and teardown can stay in separate packages.
// glue package com.example.glue.db
public class DatabaseHooks {
@Before(order = 100)
public void seedDatabase() { /* ... */ }
@After(order = 100)
public void resetDatabase() { /* ... */ }
}
// glue package com.example.glue.app
public class AppStateHooks {
@Before(order = 200)
public void startApplication() { /* ... */ }
@After(order = 200)
public void stopApplication() { /* ... */ }
}
Each scenario then runs seedDatabase → startApplication → stopApplication → resetDatabase. Pairing an @After with its @Before at the same order value gives LIFO teardown automatically, because the two annotation types sort in opposite directions.
1000 have no relative order, and nothing in the contract lets you tie-break by class name, package name, or discovery sequence. Every hook whose position you care about needs a distinct value.io.cucumber artifacts. Older lines used the cucumber.api.java annotations with a different default order, and glue scanning has changed across major versions, so validate the convention against the exact version you run.The ordering guarantee is per scenario. With Cucumber 7 on the JUnit Platform, parallelism is enabled through the cucumber.execution.parallel.* configuration parameters; scenarios then run concurrently, and each scenario's hooks still execute in the defined sequence on that scenario's thread. What ordering does not provide is synchronization between scenarios: hooks from different scenarios can interleave in wall-clock time, so static fields, shared connection pools, or shared files touched inside hooks remain unsafe. Keep hook state scenario-scoped — Cucumber instantiates glue classes per scenario by default — rather than in statics.
If your parallelism instead comes from multiple runner forks (for example Surefire forkCount), each JVM loads glue independently, but the recommendation is unchanged: the order value is annotation metadata evaluated per scenario, not a discovery artifact, so it holds in every fork.
Because @After sorts descending, the lowest order value runs last. Reserve a low band — say 0–99 — for final cleanup, assign bands per responsibility (100 for database fixtures, 200 for application state), leave headroom between bands, and document the convention so new glue packages slot in without renumbering. Never rely on ties.
One distinction changes this advice: if "run last" means once for the whole suite rather than after each scenario, @After is the wrong tool. Cucumber 7.x provides static @AfterAll (and @BeforeAll) for suite-level lifecycle; their exact behavior under parallel execution is a separate point I would verify against your version before relying on it. Which case is yours — per-scenario or once-per-suite cleanup? That determines whether the low-band @After convention above is sufficient.
Log the first line of each hook with its class and order value, run a single scenario, and read the sequence from the output; then repeat with parallel execution enabled and confirm the per-scenario sequence holds while cross-scenario interleaving appears. That observation, checked against the Cucumber 7.x reference for your exact minor version, is the verification that matters here.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.