In Cucumber-JVM, how does the order attribute sequence @Before hooks, and which way do @After hooks run?
answer
- setup and teardown mirror each other
- one attribute, two directions
- lowest number opens, highest number closes
- unset values are not an ordering
basics
~20 sCucumber-JVM runs @Before hooks in ascending order value, lowest first, and @After hooks in descending order, highest first, so teardown unwinds setup in reverse. Hooks that leave order unset share one default and have no guaranteed sequence.
solid answer
~50 s`@Before` and `@After` in Cucumber-JVM take an `order` attribute, written `@Before(order = 10)`. Before hooks run lowest value first; After hooks run highest value first. The two directions are deliberate: they make teardown unwind setup, so a hook that opens the depot database at `order = 10` and one that seeds the hire ledger at `order = 20` are torn down ledger-first, connection-last, and nothing tears down a resource another hook still needs. `order` only sequences hooks of the same kind. Scope already fixes the rest: every `@Before` runs before the first `@BeforeStep`. Hooks that do not set `order` all carry the same default value, so their relative sequence falls out of glue discovery and can change when you add a class — if one must precede another, give both an explicit value. cucumber-js has no numeric attribute and uses definition order instead, with `After` hooks reversed.
code
java · 25 linesimport io.cucumber.java.After;
import io.cucumber.java.Before;
public class DepotOrderedHooks {
@Before(order = 10)
public void openDepotConnection() {
DepotDb.open();
}
@Before(order = 20)
public void seedHireLedger() {
DepotDb.seedLedger();
}
@After(order = 20)
public void clearHireLedger() {
DepotDb.clearLedger();
}
@After(order = 10)
public void closeDepotConnection() {
DepotDb.close();
}
}go deeper
Know that the annotation takes an order attribute at all, and that lower numbers run first for setup. You are unlikely to be pressed further than that.
Explain both directions and why they differ: setup ascends, teardown descends, so resources are released in the reverse order they were acquired.
Be ready to diagnose the flake it causes — hooks left at the default value have no guaranteed sequence, so an unrelated class rename can reorder them and break teardown.
Argue about whether ordering should exist at all: coupled hooks are usually one hook, and a suite with a numbering scheme needs a written convention or it drifts.
## The attribute and the two directions `io.cucumber.java.Before` and `io.cucumber.java.After` each expose two attributes: a tag expression (the annotation's value) and `order`, an integer. You write it as `@Before(order = 10)`, or combined as `@Before(value = "@depot-db", order = 20)`. The sequencing rule is one line with a twist: * **`@Before` hooks run in ascending `order`** — the lowest number goes first. * **`@After` hooks run in descending `order`** — the highest number goes first. That asymmetry is not a quirk to memorise for its own sake; it is what makes hooks behave like a stack. Setup pushes, teardown pops. If you gave both directions the same sense, the hook that opened a resource would be torn down before the hook that depends on it had finished with it. ## A worked pair In a scaffolding-hire depot suite: | hook | `order` | runs at position | |---|---|---| | `@Before` open depot database connection | 10 | first | | `@Before` seed the hire ledger | 20 | second | | `@After` clear the hire ledger | 20 | first | | `@After` close depot database connection | 10 | second | The ledger is cleared while the connection is still open, and the connection closes last. Reverse either direction and the teardown throws, usually as a confusing "connection closed" error attributed to the wrong scenario. ## What `order` does not do 1. **It does not sequence across scopes.** Scope nesting already guarantees that all `@BeforeAll` hooks precede all `@Before` hooks, which precede the first `@BeforeStep`. Setting `order = 1` on a `@BeforeStep` will never pull it ahead of a scenario hook. 2. **It does not create a total ordering by itself.** Ordering is only as good as the values you set. Two hooks that both leave `order` at its default share the same value, and their relative sequence then depends on how the glue was discovered — which can change when a class is added, renamed, or moved to another package. A suite that passes for eleven months and then fails after an unrelated rename usually has exactly this bug. 3. **It does not order tagged hooks differently.** A tag expression on the annotation decides *whether* the hook runs for a given scenario; `order` decides *when* among the hooks that did qualify. ## Across the family | implementation | how hooks are sequenced | teardown direction | |---|---|---| | Cucumber-JVM | numeric `order` attribute on `@Before` / `@After` | `@After` runs highest value first | | cucumber-js | the order the hooks were defined and loaded | `After` runs in reverse definition order | | Behave | there is one `before_scenario` / `after_scenario` function in `environment.py`, so you sequence statements inside it | you write the reverse yourself | | SpecFlow/Reqnroll | a numeric ordering property on the hook attributes | teardown attributes unwind | Behave is worth calling out because the question does not really arise there: with a single function per scope you order by writing lines in order, and the cost is that every conditional branch for every tag lives in one file. ## When you should reach for it — and when not to Explicit ordering is a smell in small doses and a necessity in large ones. Two hooks that must run in a given sequence are, by definition, coupled; the cheapest fix is often to merge them into one hook where the sequence is just two statements. Reach for `order` when the hooks genuinely belong to different concerns that are tagged differently — a database hook that runs for `@depot-db` scenarios and a tracing hook that runs for all of them cannot be merged without dragging the tag condition into the wrong place. Whatever you decide, write the values sparsely: 10, 20, 30 rather than 1, 2, 3, so a hook can be inserted later without renumbering the file. And if you find a suite where seven hooks all carry explicit values and three do not, treat the three as unordered, because that is exactly what they are. ## The bug it produces when you ignore it The failure mode is worth rehearsing because it does not look like an ordering bug. A scaffolding-hire depot suite had two unordered `@After` hooks: one closed the depot connection, one wrote a hire-audit row through that same connection. For eleven months the glue happened to be discovered in the lucky order and 1,246 scenarios passed. A package rename flipped it, and 318 scenarios began failing in teardown with a closed-connection error that named neither hook and pointed at whichever scenario happened to be running. Three things make that diagnosable next time: 1. **Suspect ordering whenever a failure appears without a code change to the failing area.** Discovery order is an input to the run even though nothing in the diff mentions it. 2. **Read teardown failures backwards.** An error about a resource being unavailable usually means the hook that released it ran too early, not that the hook using it is wrong. 3. **Give both hooks a value the moment you find them.** One-sided ordering fixes nothing: the hook you left alone still sits at the default and can still land on either side.
- Two @Before hooks in different glue classes both leave order unset. What sequences them?Nothing you can rely on. They share the same default value, so the sequence falls out of glue discovery and can shift when a class is added or a package renamed. If one genuinely must precede the other, give both explicit values — or merge them into a single hook where the sequence is two consecutive statements.
- Does order sequence a @Before hook relative to a @BeforeStep hook?No. Scope nesting decides that: every scenario-scope `@Before` runs before the first `@BeforeStep` of that scenario, whatever numbers you set. `order` only compares hooks of the same kind with each other.
Hooks behave like a stack of nested wrappers: the first coat you put on is the last one you take off, which is exactly why the two directions run opposite ways.
saying these in an interview costs you the question
- Assumes @After hooks also run lowest value first
- Relies on class or file order to sequence unordered hooks
- Thinks order sequences hooks across different scopes
- Numbers hooks 1, 2, 3 leaving no room to insert
- Believes cucumber-js has the same numeric attribute