How do you control the order in which JUnit 5 runs whole test classes, including inner classes marked @Nested, and what are the limits of that control?
answer
- @TestClassOrder on the ENCLOSING class -> orders @Nested
- Top-level classes: junit.jupiter.testclass.order.default only
- ClassOrderer: ClassName / DisplayName / OrderAnnotation / Random
- JUnit 5.8+
- Orders starts, not completions, under parallel execution
basics
~10 sUse @TestClassOrder(ClassOrderer.OrderAnnotation.class) on an enclosing class to order its @Nested classes, with @Order on each nested class. Top-level classes are ordered only via the junit.jupiter.testclass.order.default configuration parameter. Built-in orderers: ClassName, DisplayName, OrderAnnotation, Random.
solid answer
~50 sJUnit 5.8 added a class-level counterpart to method ordering. - **`@TestClassOrder(ClassOrderer.X.class)`** is placed on an **enclosing** class and orders its `@Nested` test classes. Combine with `@Order(n)` on each nested class when using `ClassOrderer.OrderAnnotation`. - **Top-level classes** cannot be ordered by an annotation — there is no enclosing class to annotate. You set the configuration parameter `junit.jupiter.testclass.order.default` (typically in `junit-platform.properties`) to a `ClassOrderer`, which then applies to top-level classes and to nested classes that have no explicit `@TestClassOrder`. Built-in orderers: `ClassOrderer.ClassName`, `DisplayName`, `OrderAnnotation`, `Random`. The limits matter. Ordering is applied by the Jupiter engine after discovery, within one JVM and one engine — so it says nothing about interleaving with other engines, and it only fixes the order classes are *started* if the suite runs in parallel. Class order is also a poor foundation for test dependencies: it is the coarsest possible coupling, and it silently breaks the moment someone runs one class alone.
code
java · 15 lines@TestClassOrder(ClassOrderer.OrderAnnotation.class)
class AccountLifecycleTest {
@Order(1)
@Nested
class WhenOpened {
@Test void hasZeroBalance() { }
}
@Order(2)
@Nested
class WhenFrozen {
@Test void rejectsWithdrawals() { }
}
}go deeper
Knowing that @Nested classes can be ordered with @TestClassOrder plus @Order is enough at this level.
State both levers — the annotation for nested classes, the configuration parameter for top-level ones — and name the four ClassOrderers.
Emphasise the limits: engine-scoped, post-discovery, per-shard, and start-order-only under parallelism, plus why class-level fixture dependencies are the wrong design.
Argue about suite architecture: shared fixtures as resources rather than predecessors, ordering reserved for report readability and fail-fast smoke, and independence as the precondition for sharding.
## Two levels of ordering Jupiter has parallel APIs for the two granularities: | Granularity | Annotation | SPI | Global parameter | |---|---|---|---| | Methods in a class | `@TestMethodOrder` | `MethodOrderer` | `junit.jupiter.testmethod.order.default` | | Test classes | `@TestClassOrder` | `ClassOrderer` | `junit.jupiter.testclass.order.default` | `ClassOrderer` mirrors `MethodOrderer`: one method, `orderClasses(ClassOrdererContext)`, with a mutable `List<ClassDescriptor>` you sort in place. ## Built-in ClassOrderer implementations - **`ClassOrderer.ClassName`** — sorts by fully-qualified class name. - **`ClassOrderer.DisplayName`** — sorts by the resolved display name. - **`ClassOrderer.OrderAnnotation`** — sorts by `@Order(int)` on the class, with the usual `Integer.MAX_VALUE / 2` default for un-annotated classes. - **`ClassOrderer.Random`** — seeded shuffle, using the same `junit.jupiter.execution.order.random.seed` parameter as random method ordering, with the seed logged when it is not configured. ## The asymmetry: nested vs top-level This is the part interviewers probe. `@TestClassOrder` is placed on the **enclosing** class and governs the `@Nested` classes *inside* it: ```java @TestClassOrder(ClassOrderer.OrderAnnotation.class) class AccountLifecycleTest { @Order(1) @Nested class WhenOpened { @Test void a() {} } @Order(2) @Nested class WhenFrozen { @Test void b() {} } } ``` For top-level classes there is nothing to annotate — no enclosing element exists — so the only lever is the configuration parameter: ``` junit.jupiter.testclass.order.default = org.junit.jupiter.api.ClassOrderer$OrderAnnotation ``` With that in place, `@Order` on a top-level test class becomes meaningful. The parameter also serves as the default for nested classes whose enclosing class carries no `@TestClassOrder`, and an explicit `@TestClassOrder` always wins over the global default for the classes it governs. ## What the ordering does and does not reach **It is engine-scoped and post-discovery.** Jupiter discovers the classes it was asked to run, then applies the orderer to that set. Classes never discovered are not ordered into existence, and a class excluded by a filter simply is not there. **It is per JVM / per engine.** If a run spans multiple test engines, ordering inside Jupiter says nothing about how Jupiter's classes interleave with another engine's. Likewise, once a suite is split across several parallel runners or CI shards, a global class order is not a global guarantee — each runner orders only the classes it received. **Under parallel execution it orders starts, not completions.** Classes may be started in the configured order and still overlap in time. If a sequence must actually hold, the classes involved need to be pinned to sequential execution or serialised on a shared resource lock. **Nesting is depth-first.** For `@Nested` classes the outer container's own tests and its nested containers form a tree; ordering rearranges siblings at a level, it does not flatten the tree or let a nested class jump outside its parent. ## When class ordering is legitimate Three honest uses: 1. **Report readability.** `@Order` on `@Nested` classes so the HTML report tells the story in the order a human would explain it: *when created → when updated → when deleted*. No test depends on another; the ordering is documentation. 2. **Failing fast.** Put the cheap smoke class first so a broken environment fails in seconds rather than after the slow integration classes. 3. **Randomising deliberately.** `ClassOrderer.Random` globally, to prove no class depends on another class having run. ## When it is a smell Using class order to create a dependency — class B needs the fixture class A built — is the coarsest coupling available and breaks under everything: running one class from the IDE, tag filters, sharding across CI agents, rerunning failures, and parallel execution. It also destroys failure attribution: one broken class turns the whole downstream suite red. The usual healthy alternative is to make the expensive setup a *resource* rather than a *predecessor* — a container or schema started once and shared read-only, with per-test data created and cleaned by whoever needs it. Then class order becomes purely cosmetic, which is where you want it. ## Quick decision guide - Want nested classes to read in a sensible order? `@TestClassOrder(ClassOrderer.OrderAnnotation.class)` + `@Order`. - Want a policy for the whole suite (smoke first, or randomised)? Set `junit.jupiter.testclass.order.default`. - Want class B to see class A's leftovers? Do not. Give B its own setup, or share an immutable fixture.
- You put @TestClassOrder(ClassOrderer.OrderAnnotation.class) on a top-level test class and nothing changes about when that class runs. Why?Because the annotation orders the classes *inside* the annotated class — its @Nested children — not the annotated class itself relative to its siblings. Top-level classes have no enclosing element to annotate, so their ordering comes only from the junit.jupiter.testclass.order.default configuration parameter.
- Does class ordering survive when the suite is split across several CI agents?No. Ordering is applied by the Jupiter engine to the classes that a particular run discovered, so each shard orders only its own subset. Any sequencing you believed in across the whole suite disappears the moment the suite is partitioned — another reason to keep class order cosmetic rather than load-bearing.
saying these in an interview costs you the question
- Expecting @TestClassOrder on a top-level class to position that class among its siblings.
- Believing @Order on a top-level class works without the junit.jupiter.testclass.order.default parameter.
- Confusing ClassOrderer with MethodOrderer, or using MethodOrderer.OrderAnnotation inside @TestClassOrder.
- Assuming class ordering guarantees non-overlap when parallel execution is enabled.
- Building fixture dependencies between test classes and calling class ordering the solution.