skip to content

A JUnit 4 test class declares three `@Rule` fields and one of them must set up before the others. What order does JUnit apply multiple rules in by default, and how do you make that order deterministic?

level: seniorimportance: should knowfreq 28%

answer

  1. Default = reflection order = undefined
  2. Methods first, then fields (fields end up outer)
  3. @Rule(order = N), 4.13+: higher value = INNER
  4. DEFAULT_ORDER = -1
  5. RuleChain.outerRule(a).around(b) — first named is outermost

basics

~20 s

By default the order across several rule fields depends on the JVM's reflection order and is undefined. Pin it with @Rule(order = N) (JUnit 4.13+), where a lower value is the outer rule that starts first, or by combining rules into one RuleChain field.

solid answer

~50 s

Out of the box the order is **not** source order. JUnit documents that rules declared by methods are applied before rules declared by fields, and that among several fields (or several methods) the order depends on the JVM's reflection implementation — undefined in general. Relying on it produces a suite that works on one JDK and breaks on another. Two deterministic fixes: 1. **`@Rule(order = N)`**, added in JUnit 4.13 (and `@ClassRule(order = N)` too). A **higher** value means a more *inner* rule, so the lowest value wraps everything and its setup runs first. The default is `Rule.DEFAULT_ORDER` = -1. 2. **`RuleChain`** — build one composite rule in a single field: `RuleChain.outerRule(server).around(schema).around(transaction)`. The outermost is declared first; it starts first and finishes last. JUnit's own `RuleChain` javadoc now recommends the `order` attribute for pure ordering and keeps `RuleChain` for composing reusable rule bundles.

code

java · 14 lines
java
public class RepositoryTest {

    @Rule(order = 0)
    public ExternalResource database = new DatabaseRule();      // outermost: starts first

    @Rule(order = 1)
    public ExternalResource schema = new SchemaRule();

    @Rule(order = 2)
    public ExternalResource transaction = new TransactionRule(); // innermost: closest to the test

    @Test
    public void findsByEmail() { /* ... */ }
}

go deeper

for a junior

Know that multiple rules do not run in source order and that RuleChain or @Rule(order = …) exists to fix it.

for a middle

State the default precisely (reflection order, methods before fields) and use the order attribute or a chain correctly, including which end is outer.

for a senior

Diagnose the symptom in a real suite, choose between order and a packaged RuleChain, and question whether the coupled rules should be merged.

for a principal

Set a team convention — reusable chains from a fixtures factory, ordering pinned where it matters — so ordering never becomes tribal knowledge, and account for JUnit-version differences across repositories.

## The default is genuinely undefined The `@Rule` javadoc is explicit: with multiple annotated rules on a class, rules from **methods** are applied first, then rules from **fields**; and among several fields (or several methods) the order "depends on your JVM's implementation of the reflection API, which is undefined, in general". Note the direction of the method/field statement: field rules are applied *after* method rules, meaning the statements from fields execute *around* those from methods — field rules are the outer ones. The practical consequence is a class of bug that is invisible locally and appears after a JDK upgrade or on a different machine: a rule that seeds a schema runs before the rule that starts the database, and the suite fails with a connection error that has nothing to do with the test. ## Vocabulary: outer and inner Rules nest. The **outer** rule's setup runs first and its teardown runs last; the **inner** rule sits closest to the test. With three logging rules the output reads: ``` starting outer / starting middle / starting inner / <test> / finished inner / finished middle / finished outer ``` ## Fix 1 — `@Rule(order = N)` (JUnit 4.13+) ```java @Rule(order = 0) public ExternalResource server = ...; // outermost @Rule(order = 1) public ExternalResource schema = ...; @Rule(order = 2) public ExternalResource transaction = ...; // innermost ``` The javadoc states plainly: *rules with a higher value are inner*. So `order = 0` wraps `order = 2`; the low-numbered rule starts first and cleans up last. The default when you omit the attribute is `Rule.DEFAULT_ORDER`, which is -1 — lower than any non-negative value you assign, so an unannotated rule ends up outside the ones you have numbered from 0 upwards. If you are pinning order, pin it on every rule in the class rather than half of them. `@ClassRule(order = N)` behaves identically at class scope. ## Fix 2 — `RuleChain` ```java @Rule public final TestRule chain = RuleChain .outerRule(server) .around(schema) .around(transaction); ``` `RuleChain` is itself a `TestRule`, so the whole bundle occupies one field and the ordering is written into the code. Its real strength today is **packaging**: a static factory can return a ready-made chain that several test classes reuse, so callers cannot get the order wrong. For ordering alone within one class, JUnit's documentation now steers you to the `order` attribute — `RuleChain`'s javadoc says as much. ## What to do before 4.13 On older JUnit, `RuleChain` is the only supported mechanism. The alternative people reach for — declaring rules in a base class and hoping inheritance orders them — is not a guarantee either. ## Design advice: avoid needing the order Order dependence between rules is coupling. Two rules that must run in a fixed sequence are often really one concern: a rule that starts a database and prepares its schema is more honest as a single `ExternalResource` than as two rules with an implicit contract. Reserve explicit ordering for genuinely independent concerns that happen to nest — for example a diagnostic `TestWatcher` that should sit outside everything so it still observes failures thrown by the other rules' setup. ## Interaction with `@Before`/`@After` and `@ClassRule` All `@Rule` statements sit outside every `@Before`/`@After` method, so ordering questions never involve those. All `@ClassRule` statements sit outside all `@Rule` statements, since the class-level statement wraps the whole class body. Ordering among class rules follows the same undefined-by-default story and the same `order` fix. ## Diagnosing an ordering problem Symptom: intermittent "resource not ready" failures, or teardown errors that name a resource already closed. Confirmation: add a temporary logging rule, or log in each rule's `before()`/`after()`, and read the nesting. Fix: assign explicit `order` values to every rule in the class, or merge the rules that share a lifecycle.

  • With `@Rule(order = 0)` on rule A and `@Rule(order = 5)` on rule B in the same class, which rule's setup runs first?
    A. JUnit documents that rules with a *higher* order value are the inner ones, so B is nested inside A. A's setup runs first and its teardown runs last, while B's setup runs immediately before the test. If you omit the attribute the value is `Rule.DEFAULT_ORDER` (-1), which is lower still and therefore even further out.
  • How do you order several `@ClassRule` fields?
    The same way: `@ClassRule(order = N)` from JUnit 4.13, with higher values nested further in, or by placing a `RuleChain` in a single static `@ClassRule` field. The default ordering among class rules is just as undefined as for method rules, since it also comes down to reflection order over the class's fields.

saying these in an interview costs you the question

  • Believing rules run top-to-bottom in source order — the default depends on JVM reflection order.
  • Getting the direction of `order` backwards: a higher value is the inner rule, not the first to run.
  • Thinking `RuleChain.outerRule(x)` makes x innermost; it is the outermost, starting first and finishing last.
  • Pinning `order` on only some rules and forgetting that unannotated ones default to -1, which puts them outside the numbered ones.
  • Treating order dependence as normal instead of asking whether two coupled rules should be one rule.

context