How can you apply a default time limit to every test in a JUnit 5 suite without annotating them individually, and how would you switch those limits off while stepping through a test in a debugger?
answer
- junit-platform.properties on the test classpath
- junit.jupiter.execution.timeout.default = 5 s
- bare number = seconds; ns/μs/ms/s/m/h/d
- mode: enabled | disabled | disabled_on_debug
- annotation beats specific param beats general param
basics
~10 sSet the JUnit Platform configuration parameter junit.jupiter.execution.timeout.default, e.g. '5 s', in a junit-platform.properties file on the test classpath. Setting junit.jupiter.execution.timeout.mode to disabled_on_debug makes Jupiter ignore all timeouts when a debugger is attached.
solid answer
~40 sJupiter reads JUnit Platform configuration parameters, most simply from a `junit-platform.properties` file on the test classpath (typically `src/test/resources`), and also from JVM system properties of the same name. The broad switch is `junit.jupiter.execution.timeout.default`, whose value is a number with an optional unit — `10`, `10 s`, `500 ms`, `2 m`, `1 h` — with a bare number meaning seconds. There are narrower parameters for finer control: `...timeout.testable.method.default` for test-like methods, `...timeout.test.method.default`, `...timeout.testfactory.method.default`, `...timeout.testtemplate.method.default`, and `...timeout.lifecycle.method.default` with per-callback variants such as `...timeout.beforeeach.method.default`. The more specific parameter wins, and any explicit `@Timeout` annotation beats all of them. For debugging, `junit.jupiter.execution.timeout.mode` accepts `enabled`, `disabled`, or `disabled_on_debug`; the last one detects an attached debugger and skips timeout enforcement so a breakpoint does not blow the budget. `junit.jupiter.execution.timeout.thread.mode.default` sets the suite-wide thread mode.
code
java · 4 linesjunit.jupiter.execution.timeout.default = 5 m
junit.jupiter.execution.timeout.beforeeach.method.default = 30 s
junit.jupiter.execution.timeout.mode = disabled_on_debug
junit.jupiter.execution.timeout.thread.mode.default = SAME_THREADgo deeper
Know that a junit-platform.properties file on the test classpath can set junit.jupiter.execution.timeout.default so you do not annotate every test.
Name the category-specific parameters, the value syntax with units, the precedence order, and the disabled_on_debug mode.
Argue for a generous global net plus selective tightening, explain what the defaults cannot cover (static initialisers, uninterruptible code) and why an external watchdog is still needed.
Frame it as a policy artefact: one committed configuration that is strict on CI, debugger-friendly locally, documented so the numbers are maintainable, and paired with an out-of-JVM hard stop.
## Configuration parameters, not annotations Annotating hundreds of methods is unrealistic, so Jupiter lets a suite declare defaults through *JUnit Platform configuration parameters*. These are simple key/value pairs the engine reads at startup. The most portable source is a file named `junit-platform.properties` placed at the root of the test classpath — conventionally `src/test/resources/junit-platform.properties`. The same keys can also be supplied as JVM system properties (`-Djunit.jupiter.execution.timeout.default=5s`) or programmatically through the launcher's discovery request when embedding the platform. ## The key family - `junit.jupiter.execution.timeout.default` — the catch-all: every testable and lifecycle method. - `junit.jupiter.execution.timeout.testable.method.default` — `@Test`, `@TestFactory`, `@TestTemplate` (and therefore `@ParameterizedTest`/`@RepeatedTest`, which are test templates). - `junit.jupiter.execution.timeout.test.method.default`, `...testfactory.method.default`, `...testtemplate.method.default` — one category each. - `junit.jupiter.execution.timeout.lifecycle.method.default` — all four callbacks, with `...beforeall.method.default`, `...beforeeach.method.default`, `...aftereach.method.default`, `...afterall.method.default` for individual ones. Resolution is most-specific-wins among the parameters, and any explicit `@Timeout` on the method, its class, or an enclosing class overrides all of them. A frequent real configuration is a large default for tests plus a much smaller one for `@BeforeEach`, on the theory that a per-test fixture that takes a minute is already broken. ## Value syntax The value is a positive number optionally followed by a unit: `ns` (nanoseconds), `μs`/`us` (microseconds), `ms`, `s`, `m` (minutes), `h`, `d`. Whitespace between number and unit is allowed, so `500 ms` and `500ms` both parse. A bare number means seconds. An unparseable value causes a configuration error rather than being silently ignored, which is useful — a typo surfaces immediately. ## Turning it off for debugging A suite-wide budget is hostile to interactive debugging: pause on a breakpoint for twenty seconds and every subsequent test fails on time. `junit.jupiter.execution.timeout.mode` handles that with three values: - `enabled` (default) — timeouts always apply. - `disabled` — timeouts are never enforced, annotations included. Useful as a temporary local override. - `disabled_on_debug` — Jupiter inspects the JVM's input arguments for debug-agent flags and, when it detects one, skips enforcement for that run. `disabled_on_debug` is the setting most teams want permanently, because it makes a strict CI-wide default coexist with day-to-day IDE debugging. Its detection is heuristic — it looks for the standard debug agent arguments — so an exotic launch configuration may not be recognised, and the escape hatch is then the plain `disabled` value. ## Thread-mode default `junit.jupiter.execution.timeout.thread.mode.default` takes `SAME_THREAD` or `SEPARATE_THREAD` and supplies the value that the annotation's `INFERRED` default resolves to. Out of the box it is `SAME_THREAD`. Changing it suite-wide is a heavy decision: it moves every timed method onto a worker thread, which breaks thread-bound state such as transaction synchronisation, so it is normally left alone and overridden per annotation. ## Operational advice Treat the global default as a deadlock net rather than a performance bar. Something like `junit.jupiter.execution.timeout.default = 5 m` guarantees no single method can wedge the pipeline for an hour while being far above any legitimate runtime, so it never fires spuriously on a loaded agent. Tighten selectively rather than globally. Be aware of two limits. First, these defaults apply to Jupiter-executed methods; a hang inside a static initialiser, an extension callback, or a container-level operation is outside their reach. Second, because same-thread enforcement is interrupt-based, a default of five minutes does not guarantee the run ends after five minutes — it guarantees a failure is *recorded* once the method returns. A hard guarantee still needs a watchdog outside the JVM. Finally, keep the properties file under version control and comment the chosen numbers. A bare `timeout.default = 30` in a repository is a magic number nobody dares change; a line explaining that it is a deadlock net, not a latency budget, prevents the next engineer from tightening it into flakiness.
- If junit.jupiter.execution.timeout.default is 5 seconds and a method is annotated @Timeout(30), which applies?The annotation: 30 seconds. Configuration parameters only supply defaults for methods that have no applicable @Timeout on themselves, their class, or an enclosing class. The precedence runs annotation over the most specific matching parameter over the general one, so an explicit annotation is always the way to exempt a known-slow test from a suite-wide budget.
- Why is disabled_on_debug preferred over simply removing the default while debugging?Because removing it is a shared, easily forgotten edit: someone deletes the line locally, and either commits it and loses the protection for everyone, or forgets to restore it. disabled_on_debug keeps one committed configuration that enforces budgets on CI and normal runs while automatically standing down when a debug agent is attached, so no one has to remember anything.
saying these in an interview costs you the question
- Believing the only way to set a default is annotating every class or writing a base class.
- Reading a bare number such as '30' as milliseconds instead of seconds.
- Assuming a configuration default overrides an explicit @Timeout annotation.
- Not knowing timeout enforcement can be disabled while debugging, and instead loosening the value permanently.
- Treating the suite-wide default as a latency assertion and setting it near actual runtimes.