You place JUnit Jupiter's @Timeout on a test class rather than on one method. Which methods does it then govern, and how is the effective limit resolved if a method or an inner class also declares one?
answer
- class-level = copied onto every method
- covers @BeforeAll/@BeforeEach/@AfterEach/@AfterAll
- reaches @Nested classes
- per method, never per class total
- nearest declaration wins, may be looser
basics
~20 sA class-level @Timeout applies to every testable and lifecycle method in that class and in its @Nested classes. The most specific declaration wins: a method-level annotation overrides its class, an inner class overrides the enclosing class, and any annotation overrides suite-wide defaults.
solid answer
~50 sDeclaring `@Timeout` on a test class is shorthand for putting it on every method Jupiter runs in that class — the testable methods (`@Test`, `@ParameterizedTest`, `@RepeatedTest`, `@TestFactory`, `@TestTemplate`) and the lifecycle methods (`@BeforeAll`, `@BeforeEach`, `@AfterEach`, `@AfterAll`). It also reaches into `@Nested` inner classes, so an outer-class budget covers nested test hierarchies. Importantly the budget is applied *per method*, not to the class as a whole: `@Timeout(2)` on a class with thirty tests does not mean the class must finish in two seconds; it means each method gets two seconds. Resolution is most-specific-wins. A method's own `@Timeout` overrides its declaring class; a `@Nested` class's declaration overrides the enclosing class's; and any explicit annotation overrides suite-wide configuration-parameter defaults. There is no accumulation or minimum-of rule — the nearest declaration simply replaces the outer one, including making it larger.
code
java · 23 linesimport java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.*;
@Timeout(2) // 2 seconds for EVERY method below
class CatalogIntegrationTest {
@BeforeEach
void seed() { db.seed(); } // governed by the class: 2s
@Test
void lookupIsFast() { } // governed by the class: 2s
@Test
@Timeout(value = 30, unit = TimeUnit.SECONDS)
void fullReindexIsSlowButBounded() { } // method wins: 30s
@Nested
@Timeout(5)
class Reporting {
@Test
void monthlyReport() { } // nested class wins: 5s
}
}go deeper
Say that putting it on the class applies it to all the tests in that class, and that a method annotation takes priority.
Add lifecycle-method and @Nested coverage, the per-method (not per-class) semantics, and the full precedence ladder down to configuration defaults.
Discuss when a class-level budget is the right granularity, why overrides may legitimately be looser, and how a laptop-derived class budget multiplies flakiness across an entire class on loaded CI.
Position it inside a layered policy: default net, class-level budgets for integration categories, targeted overrides, and out-of-framework enforcement for whole-suite caps.
## What class-level means `@Timeout` is declared with a target that includes types, so you can write it once above the class instead of on twenty methods. Jupiter then treats every method it invokes in that class as if it carried the annotation: - testable methods: `@Test`, `@ParameterizedTest`, `@RepeatedTest`, `@TestFactory`, `@TestTemplate`; - lifecycle callbacks: `@BeforeAll`, `@AfterAll` (once per class) and `@BeforeEach`, `@AfterEach` (once per test). The practical value is in the lifecycle coverage. Suites that hang usually hang in setup — a database container that never becomes ready, a WireMock server bound to a busy port, a pool waiting for a connection. A class-level annotation catches those without you remembering to annotate each callback. ## Per method, not per class This is the misconception interviewers probe. The annotation never expresses a budget for the whole class or the whole run; it is copied onto each method. With `@Timeout(5)` on a class holding fifty tests, the class may legitimately take four minutes; only an individual method exceeding five seconds fails. Likewise a `@RepeatedTest(100)` under a class-level budget gives each of the hundred repetitions its own budget, not the set of them. ## Nested classes and inheritance Jupiter's `@Nested` inner classes inherit the enclosing class's applicable timeout, so an outer declaration protects the whole hierarchy. Inheritance from a superclass or an interface follows Jupiter's normal rules for inherited annotations: a test class extending an abstract base whose class-level `@Timeout` is present is governed by it, and inherited lifecycle methods are governed by the annotation applicable where they are resolved. ## The precedence ladder From weakest to strongest: 1. Suite-wide defaults expressed as JUnit Platform configuration parameters, including the category-specific ones (a default just for `@BeforeEach`, for instance). 2. `@Timeout` on an enclosing test class. 3. `@Timeout` on the immediate (possibly `@Nested`) class. 4. `@Timeout` on the method itself. The nearest declaration wins outright. It does not intersect with outer declarations, and it is not required to be stricter: a method may legally raise its budget above the class's, which is the intended escape hatch for the one slow test in an otherwise fast class. There is no way to express "whichever is smaller"; if you want that, you have to write the number you mean. ## Thread mode interacts the same way `threadMode` travels with the annotation, so a class-level `@Timeout(value = 5, unit = SECONDS, threadMode = SEPARATE_THREAD)` puts every method in the class — including lifecycle callbacks — on a separate thread. That is rarely what you want in a class using thread-bound state such as a Spring test transaction, so class-level declarations are usually left at the inferred (same-thread) mode, with the aggressive mode applied narrowly at method level. ## Practical patterns A common layering in real suites: no annotation at all on the majority of classes, relying on a generous suite-wide default as a deadlock net; a class-level annotation on integration-test classes whose setup talks to containers or networks; and method-level overrides in two directions — tighter on a method that must not hang, looser on the single known-slow test. Where a whole category of tests shares a budget, some teams put `@Timeout` on a custom annotation applied to those classes so the number lives in one place. The review guidance is the same as for method-level use: the class budget should sit far enough above the slowest legitimate method that ordinary CI noise cannot reach it. A class-level number chosen from a laptop's timings is a flakiness generator multiplied across every method in the class, because a single overloaded agent can now trip many tests at once and make the failure look like a systemic outage rather than a slow machine.
- A class is annotated @Timeout(2) and holds 40 tests. The class takes 90 seconds in total. Does anything fail?No, provided no single method exceeded two seconds. The class-level annotation is applied to each method independently, so the total wall-clock time of the class is irrelevant. JUnit Jupiter offers no annotation that budgets an entire class or suite; if you need an overall cap you have to enforce it outside the framework, at the build or CI-job level.
- Can a method-level @Timeout be longer than the class-level one, or must it be stricter?It can be longer. Resolution is nearest-declaration-wins, not minimum-wins, so the method's value simply replaces the class's. That is the intended way to exempt one genuinely slow test from a tight class budget. The flip side is that a loose method annotation silently removes the class-level protection, so such overrides deserve a comment explaining why.
saying these in an interview costs you the question
- Reading a class-level @Timeout as a budget for the whole class or the whole test run.
- Believing the class-level value only covers @Test methods and not setup or teardown.
- Assuming the stricter of the class and method values applies, rather than the nearest declaration.
- Thinking @Nested classes escape an enclosing class's timeout.
- Setting a class-level threadMode = SEPARATE_THREAD without realising it also moves lifecycle callbacks off the test thread.