Hook Execution Order in Cucumber JVM

What Is Hook Execution Order?

Hook execution order in Cucumber JVM defines the sequence in which multiple hooks run during scenario execution. A project may have more than one @Before hook, more than one @After hook, and sometimes multiple @BeforeStep or @AfterStep hooks. When several hooks of the same type exist, Cucumber needs a deterministic way to decide which setup method runs first and which cleanup method runs later. The order attribute is used to control that sequence.

In simple terms, hook execution order determines the order in which setup and cleanup methods execute. This matters because many hooks depend on each other. Configuration may need to load before the driver is initialized. The driver may need to exist before the application URL can be opened. Screenshots must be captured before the browser is closed. Test data should be deleted before API or database clients are disconnected. If these actions happen in the wrong order, the framework becomes unstable.

Hook ordering is part of lifecycle design. It is not only a syntax detail. A well-ordered hook sequence makes the test setup predictable and the teardown safe. A poorly ordered hook sequence can create confusing failures that appear in step definitions even though the root cause is setup or cleanup. For example, if the browser opens before configuration is loaded, the framework may not know which environment URL to use. If the browser is closed before the screenshot hook runs, failure evidence is lost.

Cucumber does not require every project to use explicit hook ordering. If there is only one hook of each type, ordering is not a concern. But as soon as multiple hooks of the same type exist, explicit order values are recommended. Relying on method declaration order, class order, or file order is not a safe framework design. The order attribute communicates intent clearly to both Cucumber and future maintainers.

Why Hook Execution Order Is Important

Setup usually has dependencies. Consider a common Selenium sequence: read configuration, initialize WebDriver, open the browser, and open the application. This sequence is logical because driver setup depends on configuration, and opening the application depends on knowing the URL. If the order is reversed, the framework may try to launch a browser without knowing which browser type to use or may try to open an application before the base URL is available.

Wrong Order:
Launch Browser
      |
      v
Read URL

The correct setup order is:

Read Configuration
      |
      v
Initialize Driver
      |
      v
Launch Browser
      |
      v
Open Application

Cleanup also has dependencies, but cleanup often runs in reverse. If a scenario fails, the framework may need to capture logs and screenshots before closing the browser. It may need to delete test data before closing API or database connections. It may need to flush reports after attaching screenshots and logs. Teardown order protects evidence and ensures resources are released safely.

When hook order is unmanaged, failures become hard to diagnose. A screenshot may not appear because the browser was already closed. A cleanup call may fail because the database connection was disconnected earlier. A report may be incomplete because it was flushed before attachments were added. Explicit ordering prevents these avoidable problems.

Default Hook Execution Without Order

If multiple hooks of the same type are defined without an order value, developers should not rely on the order in which they appear in the source file. The execution order may not be what the team expects. It may depend on class loading, reflection, glue scanning, runtime behavior, or implementation details that should not be treated as a contract.

@Before
public void hook1() {
}

@Before
public void hook2() {
}

In this example, it may look like hook1 should run before hook2, but that assumption is unsafe. If hook2 depends on hook1, the framework should say so explicitly using the order attribute. Explicit ordering makes the lifecycle deterministic and easier to review.

Default ordering may be acceptable only when hooks are truly independent. For example, if one hook logs a message and another initializes an unrelated metric, order may not matter. But most real setup and cleanup hooks have at least some dependency. It is better to be explicit when multiple hooks exist.

The order Attribute

The order attribute tells Cucumber when a hook should execute relative to other hooks of the same type. It is written inside the hook annotation. The value is an integer. Smaller and larger values have different meanings depending on whether the hook is a setup hook or a teardown hook.

@Before(order = 1)
public void setup() {
}

The order value is not a delay or priority in the general testing sense. It is a lifecycle sequence value. For @Before and @BeforeStep, lower numbers execute first. For @After and @AfterStep, higher numbers execute first. This difference is one of the most important details to remember.

Use simple values such as 1, 2, 3, and 4 when possible. Large arbitrary values such as 1000, 250, and 9999 make the sequence harder to read. Sequential values communicate the lifecycle more clearly. If a new hook needs to be inserted later, values can be adjusted or spaced intentionally, but clarity should remain the priority.

@Before Hook Order

For @Before hooks, smaller order values execute first. This is natural for setup because foundational preparation should run before dependent preparation. Configuration usually comes before driver initialization. Driver initialization usually comes before opening the application. Report setup may come before logging scenario setup details.

@Before(order = 1)
public void readConfiguration() {
    System.out.println("Configuration");
}

@Before(order = 2)
public void initializeDriver() {
    System.out.println("Driver");
}

@Before(order = 3)
public void launchBrowser() {
    System.out.println("Browser");
}

The execution is:

Configuration
      |
      v
Driver
      |
      v
Browser
      |
      v
Scenario

This is predictable and readable. A maintainer can look at the hook annotations and understand the setup flow. If the driver hook requires configuration, the order value makes that dependency clear. If the application opening hook requires a driver, the sequence makes that clear too.

Visualizing @Before Hook Order

A simple way to remember @Before ordering is that Cucumber walks upward from the lowest value to the highest value before the scenario begins. The lower number has higher setup priority. It runs earlier because later setup often depends on it.

@Before(order = 1)
      |
      v
@Before(order = 2)
      |
      v
@Before(order = 3)
      |
      v
Scenario Starts

This makes @Before ordering similar to building a foundation. First load the basic settings. Then create the driver or client. Then open the application or prepare the scenario data. Each layer is built on top of the previous layer. If the foundation is missing, later setup may fail.

@After Hook Order

For @After hooks, the order is reversed. Higher order values execute first, and lower order values execute later. This surprises many beginners because they expect the same ordering rule as @Before. However, reverse ordering makes sense for teardown. Cleanup often needs to undo setup in the opposite direction.

@After(order = 3)
public void captureLogs() {
}

@After(order = 2)
public void cleanupData() {
}

@After(order = 1)
public void closeBrowser() {
}

The execution is:

Scenario Ends
      |
      v
Capture Logs
      |
      v
Cleanup Data
      |
      v
Close Browser

This order ensures that evidence and data cleanup happen while required resources are still available. If the browser is closed first, screenshot or browser log capture may fail. If API clients or database connections are closed too early, cleanup may fail. Higher order values allow diagnostic and cleanup tasks to run before final resource release.

Visualizing @After Hook Order

A simple way to remember @After ordering is that Cucumber walks downward from the highest value to the lowest value after the scenario ends. The higher number executes earlier in teardown. The lower number executes later.

Scenario Ends
      |
      v
@After(order = 3)
      |
      v
@After(order = 2)
      |
      v
@After(order = 1)

This makes cleanup behave like unwinding a stack. The last resources prepared are often the first resources cleaned. The final low-order teardown often closes foundational resources such as browser sessions, database connections, report managers, or context containers.

Why @After Order Is Reversed

The reversed @After order follows a common resource-management idea: create resources in one order and release them in reverse order. This is often called Last In, First Out, or LIFO. If setup opens the browser, connects to a database, and starts reports, teardown may need to stop reports, disconnect the database, and close the browser. The exact sequence depends on the framework, but reverse cleanup prevents dependent resources from being destroyed too early.

Setup:
Open Browser
      |
      v
Connect Database
      |
      v
Start Reports

Cleanup:
Stop Reports
      |
      v
Disconnect Database
      |
      v
Close Browser

In practice, teardown should also consider diagnostics. If a scenario fails, screenshots and logs should be collected before the browser is closed or report is flushed. If scenario data must be deleted through an API, the API client must still be available. If cleanup depends on database state, the database connection must remain open until cleanup is complete.

The reverse order rule helps maintain this discipline. It gives the team a predictable way to coordinate teardown without relying on accidental method order.

Complete Hook Order Example

A complete example shows setup and teardown together. The @Before hooks run from lowest order to highest order. The scenario executes. Then the @After hooks run from highest order to lowest order.

@Before(order = 1)
public void loadConfig() {
}

@Before(order = 2)
public void initializeDriver() {
}

@Before(order = 3)
public void openApplication() {
}

@After(order = 3)
public void captureLogs() {
}

@After(order = 2)
public void cleanupData() {
}

@After(order = 1)
public void closeBrowser() {
}

The lifecycle becomes:

@Before(1)
      |
      v
@Before(2)
      |
      v
@Before(3)
      |
      v
Scenario
      |
      v
@After(3)
      |
      v
@After(2)
      |
      v
@After(1)

This structure is easy to reason about. It makes setup and cleanup visible in the code. It also makes dependency mistakes easier to detect during review.

Hook Execution with Scenario Steps

Step-level hooks fit inside the scenario execution flow. Cucumber runs @Before hooks first. Then before each step, it runs matching @BeforeStep hooks. After each step, it runs matching @AfterStep hooks. Finally, after the scenario ends, it runs @After hooks.

@Before
      |
      v
@BeforeStep
      |
      v
Given step
      |
      v
@AfterStep
      |
      v
@BeforeStep
      |
      v
When step
      |
      v
@AfterStep
      |
      v
@BeforeStep
      |
      v
Then step
      |
      v
@AfterStep
      |
      v
@After

This flow is useful for understanding step-level logging and screenshots. If a framework captures screenshots after every step, that happens in @AfterStep. If it starts a timer before every step, that happens in @BeforeStep. These hooks run many times, so ordering and performance both matter.

Multiple @BeforeStep Hooks

For @BeforeStep hooks, the order rule is the same as @Before. Lower order values execute first. This makes sense because @BeforeStep is a preparation hook for an individual step.

@BeforeStep(order = 1)
public void beforeStepOne() {
}

@BeforeStep(order = 2)
public void beforeStepTwo() {
}

The execution before each step is:

BeforeStep(1)
      |
      v
BeforeStep(2)
      |
      v
Execute Step

Use step-level setup sparingly. If a hook runs before every step, even small overhead can add up across hundreds of scenarios. Step hooks should be lightweight and should not perform expensive setup such as launching browsers, loading large files, or creating test data.

Multiple @AfterStep Hooks

For @AfterStep hooks, the order rule is the same as @After. Higher order values execute first. This allows step-level diagnostics or cleanup to happen before lower-order finalization.

@AfterStep(order = 2)
public void afterStepTwo() {
}

@AfterStep(order = 1)
public void afterStepOne() {
}

The execution after each step is:

Execute Step
      |
      v
AfterStep(2)
      |
      v
AfterStep(1)

A practical example is screenshot capture and step report update. A higher-order @AfterStep hook may capture a screenshot while the browser state is still available. A lower-order hook may then update the report or clear step context. The sequence should protect the evidence needed for debugging.

Hook Order Summary

The hook ordering rules can be summarized clearly. @Before hooks execute from lowest order to highest order. @BeforeStep hooks also execute from lowest order to highest order. @After hooks execute from highest order to lowest order. @AfterStep hooks also execute from highest order to lowest order.

Hook Type       Execution Order
@Before         Lowest order first
@After          Highest order first
@BeforeStep     Lowest order first
@AfterStep      Highest order first

This table is one of the most important interview points for Cucumber hooks. Remember that setup moves upward from low to high, while teardown moves downward from high to low. This mental model prevents most hook-order confusion.

Tagged Hooks and Order

Tagged hooks also respect the order attribute. A hook can be both tag-specific and ordered. This is useful when a group of scenarios needs a specific setup sequence. For example, smoke scenarios may need smoke setup and report initialization. UI scenarios may need browser setup. API scenarios may need API client setup.

@Before(value = "@Smoke", order = 1)
public void smokeSetup() {
}

@Before(value = "@Smoke", order = 2)
public void initializeReports() {
}

For a scenario tagged @Smoke, execution is:

Smoke Setup
      |
      v
Initialize Reports
      |
      v
Smoke Scenario

Tagged hook ordering is powerful, but it should remain readable. Too many overlapping tagged hooks can make lifecycle behavior difficult to predict. Use tags for meaningful scenario categories and keep hook order simple.

Real-Time Selenium Example

A Selenium framework often needs ordered setup and teardown. Configuration should load before driver initialization. The driver should initialize before the application opens. After execution, diagnostics should be captured before the browser closes. Data or cookies may be cleaned before quitting the driver.

@Before(order = 1)
public void loadProperties() {
}

@Before(order = 2)
public void initializeDriver() {
}

@Before(order = 3)
public void openApplication() {
}

@After(order = 3)
public void captureScreenshot() {
}

@After(order = 2)
public void clearCookies() {
}

@After(order = 1)
public void quitDriver() {
}

The execution sequence is:

Read Config
      |
      v
Initialize Driver
      |
      v
Open App
      |
      v
Scenario
      |
      v
Capture Screenshot
      |
      v
Clear Cookies
      |
      v
Quit Driver

This order protects failure evidence and releases resources correctly. If the driver were quit before the screenshot hook, the screenshot would fail. If configuration loaded after driver initialization, browser selection or grid URL may be wrong.

Real-Time API Example

API automation also benefits from explicit hook ordering. Environment configuration should load before Rest Assured or the API client is initialized. Authentication may happen after the base URI is known. Cleanup should delete created records before clients or connections are closed.

@Before(order = 1)
public void loadEnvironment() {
}

@Before(order = 2)
public void initializeRestAssured() {
}

@After(order = 2)
public void deleteCreatedUsers() {
}

@After(order = 1)
public void closeConnections() {
}

The execution sequence is:

Environment
      |
      v
REST Assured
      |
      v
Scenario
      |
      v
Delete Users
      |
      v
Close Connections

This sequence keeps cleanup functional. If connections close first, delete calls may fail. If environment setup happens after the API client is initialized, requests may point to the wrong endpoint.

Enterprise Hook Sequence Example

An enterprise Cucumber JVM framework may use several setup and teardown responsibilities. The setup may read configuration, initialize the driver, open the application, create test data, and start scenario context. The teardown may capture evidence, delete data, close the browser, clear context, and generate reports. Explicit ordering keeps this lifecycle deterministic.

@Before(order = 1)  Read Configuration
@Before(order = 2)  Initialize Driver
@Before(order = 3)  Open Application
@Before(order = 4)  Create Test Data

Scenario

@After(order = 4)   Capture Screenshot
@After(order = 3)   Delete Test Data
@After(order = 2)   Close Browser
@After(order = 1)   Generate Report

This sequence creates resources in a clear order and releases or finalizes them in a controlled reverse order. The exact order may differ by framework, but the lifecycle should always be intentional. If reports need screenshots attached before final generation, screenshot capture must happen before report finalization. If data cleanup requires an API client, close the API client after cleanup.

Mapping Hook Dependencies Before Assigning Order

Before assigning order values, it is useful to map hook dependencies. A hook dependency exists when one hook needs another hook to complete first. Driver initialization may depend on configuration. Opening the application depends on the driver and URL. Creating UI-based test data may depend on a logged-in browser session, while API-based test data may depend on API client setup. Teardown has similar dependencies. Screenshot capture depends on the browser still being open. Data cleanup depends on having the created record identifiers and the required API or database connection.

A simple dependency review prevents random ordering. Instead of adding numbers casually, ask what must exist before this hook can run. If the answer is configuration, give configuration a lower @Before order. If the answer is an active browser, make sure browser teardown happens after screenshot capture. If the answer is a report entry, start the report before attaching step evidence and flush it only after attachments are complete.

This dependency-first approach is better than choosing order values based on personal preference. It also makes code reviews easier. When someone adds a new hook, reviewers can ask where it fits in the lifecycle. Does it prepare something? Does it depend on something? Does something else depend on it? The order value should reflect those answers.

Setup dependency example:
Config -> Driver -> Application -> Test Data

Cleanup dependency example:
Screenshot -> Test Data Cleanup -> Driver Quit -> Report Flush

In large frameworks, teams may document hook order in a small lifecycle diagram or README. This is useful because hooks are often spread across multiple classes. A documented sequence helps new team members understand why the order values exist and prevents accidental lifecycle changes.

Hook Order and Reporting

Reporting is one area where hook order matters a lot. A report entry may need to start before setup logs are attached. Screenshots and failure logs must be attached before the report is flushed. If the report is flushed too early, later evidence may be missing. If the browser is closed before screenshot capture, the report may contain a failure without visual context.

A practical reporting sequence starts report context early, then allows the scenario and diagnostics to add information, and finally flushes the report at the end. For example, a @Before(order = 1) hook may create the report entry, while a @After(order = 3) hook captures screenshots and logs, and a @After(order = 1) hook flushes the final report. Since higher @After values run first, the diagnostic hook runs before the final flush hook.

@Before(order = 1)
Start Report Entry

Scenario Runs

@After(order = 3)
Attach Screenshot and Logs

@After(order = 1)
Flush Report

This structure keeps reports complete. It also prevents a common mistake where report finalization happens before attachments are added. When a framework has flaky or missing screenshots, hook order is one of the first things to check.

Hook Order in Parallel Execution

Parallel execution does not change the basic order rules, but it raises the cost of poor hook design. Each scenario still follows its own hook lifecycle, but multiple scenarios may be moving through that lifecycle at the same time. If hooks use shared static variables, broad cleanup, or non-thread-safe report objects, order alone will not save the framework. Hook order must be combined with scenario isolation.

For example, every scenario should initialize and quit its own driver session. A @Before(order = 2) hook may create the driver for the current thread, and an @After(order = 1) hook may quit only that current thread driver. If teardown accidentally quits a global driver reference, it may close another scenario's browser. Similarly, cleanup should delete records created by the current scenario, not all records matching a broad test prefix.

Reports also need parallel-safe lifecycle handling. A report entry started for Scenario A should not receive screenshots from Scenario B. Scenario context, thread-local storage, dependency injection, or framework-managed scenario scopes can help keep hook state isolated. The order attribute defines sequence inside a scenario; good state management ensures one scenario's lifecycle does not interfere with another's.

Troubleshooting Hook Order Problems

Hook order problems usually appear as confusing setup or teardown failures. A step may fail because the driver is null, but the real issue is that driver initialization ran before configuration loaded. A screenshot may be missing because the browser quit first. Cleanup may fail because the API client closed before the delete request. A report may be incomplete because flush ran before attachments were added.

To troubleshoot, add clear lifecycle logs to hooks. Log the hook name, order value, scenario name, and key resource state. For example, log when configuration loads, when the driver initializes, when the application opens, when screenshots are captured, and when the driver quits. These logs make the actual order visible during execution.

@Before(order = 1) -> loadConfig
@Before(order = 2) -> initializeDriver
@Before(order = 3) -> openApplication
Scenario -> Login
@After(order = 3) -> captureScreenshot
@After(order = 2) -> cleanupData
@After(order = 1) -> quitDriver

If the observed logs do not match the intended lifecycle, check the order values, hook types, tag expressions, and glue configuration. Also check whether multiple hook classes contain hooks with overlapping responsibilities. Sometimes the problem is not the number itself but duplicate hooks doing similar work in different packages.

Common Mistakes with Hook Execution Order

Assuming File Order Determines Execution

A common mistake is assuming that hooks execute in the order they appear in a file. This is not a safe assumption. Use the order attribute when order matters.

Using Random Order Values

Values such as 1000, 250, and 75 may work technically, but they make the lifecycle harder to understand. Simple sequential values such as 1, 2, 3, and 4 are easier to maintain.

Forgetting @After Is Reversed

Many beginners expect @After(order = 1) to run first. In Cucumber, higher order values run first for @After. Forgetting this can cause diagnostics and cleanup to run too late.

Splitting Dependent Logic Without Order

If one hook depends on another, explicit order is required. Do not split dependent setup into separate hooks and hope Cucumber executes them correctly by method order.

Creating Too Many Ordered Hooks

Too many hooks can make the lifecycle difficult to reason about. If setup becomes complex, consider one hook that delegates to a setup coordinator service. The service can execute steps in a clear sequence.

Best Practices for Hook Ordering

Always specify the order attribute when multiple hooks of the same type exist and sequence matters. Use low order values for foundational setup tasks such as configuration loading and driver initialization. Use high order values for teardown tasks that should happen first, such as screenshots, logs, and cleanup that depends on active resources.

Keep numbering simple. Prefer 1, 2, 3, and 4 over arbitrary large numbers. Avoid relying on class order, method declaration order, or file structure. Use tagged hooks with order when different scenario types require different setup sequences. Keep each hook focused on a single responsibility, and delegate complex implementation to helper classes or services.

Review hook order whenever new setup or cleanup behavior is added. Ask what the new hook depends on and what depends on it. For cleanup, ask which resources must still be alive when the hook runs. These questions prevent lifecycle bugs before they reach the CI pipeline.

Interview-Ready Explanation

Hook execution order controls the sequence in which multiple Cucumber hooks execute. The order attribute determines priority. For @Before and @BeforeStep hooks, lower order values execute first. For @After and @AfterStep hooks, higher order values execute first. This reverse order for teardown supports a cleanup strategy where resources are released in the opposite order of setup.

Tagged hooks also honor the order attribute. Explicit ordering improves predictability, maintainability, and reliability because dependent setup and cleanup methods run in the correct sequence. A good framework does not rely on source code order or file order. It defines hook order clearly so setup and teardown are deterministic.

Summary

Hook execution order is a core Cucumber JVM concept for projects with multiple hooks. It defines when setup, teardown, and step-level lifecycle methods run. @Before and @BeforeStep execute from lowest order to highest order. @After and @AfterStep execute from highest order to lowest order. This difference is essential for safe setup and cleanup.

The golden rules are simple: always define order when multiple hooks of the same type exist, remember that @Before runs low to high, remember that @After runs high to low, create resources in one order and clean them in reverse order, and never rely on source code or file order for deterministic execution. Clear hook ordering makes Cucumber frameworks easier to understand, safer to extend, and more reliable in real automation suites.