JUnit @Test(timeout) exception timing relative to test method completion
0 reputation · 20 Feb 2026, 05:20 UTC
0 reputation · 20 Feb 2026, 05:20 UTC
The @Test(timeout) annotation in JUnit establishes a best-effort execution limit for test methods, throwing TestTimeoutException when the configured threshold is exceeded. While the annotation's intent is well-documented, the precise conditions governing exception delivery relative to test method termination remain ambiguous across major versions. Thread interruption mechanisms and scheduler delays can cause the exception to fire after the test method's logical conclusion, and the timeout scope--per-method in JUnit 4 versus per-invocation in JUnit 5--affects nested, parameterized, and class-level test configurations differently. This behavioral gap influences test reliability when deterministic resource cleanup or accurate execution-time reporting is required.
Timeout extension respect the same thread-interruption model as the @Test(timeout) attribute for dynamically discovered test instances?@BeforeEach and @AfterEach lifecycle methods when the test method exceeds the configured limit?TestTimeoutException prevent @After method execution in TestWatcher or RunListener hooks?34775 reputation · 20 Feb 2026, 14:23 UTC
In both JUnit 4 and JUnit 5 the @Test(timeout) (or JUnit 5 @Timeout) does not wait for the test method to finish normally; it interrupts the thread running the test and reports a TestTimeoutException from the main test‑running thread.
org.junit.internal.runners.statements.FailOnTimeout. It starts the test method in a separate thread, joins with the specified limit, calls thread.interrupt() on timeout, and then throws TestTimeoutException in the caller thread.org.junit.jupiter.api.extension.Timeout extension uses an ExecutorService to run the test invocation, interrupts the task’s thread on timeout, and fails the invocation with TestTimeoutException. The extension is applied per test invocation, so it works the same for dynamically generated tests (@TestFactory).@After in JUnit 4, @AfterEach/@AfterAll in JUnit 5) are **not** invoked for that invocation because the test is considered failed before the after‑hooks can run.TestWatcher (or any RunListener) receives testFinished after the test method execution (including the timeout interruption) and before the exception propagates further, so the listener sees the failure.The TestTimeoutException can appear **after** the test method’s logical end because:
Thread.interrupted() (e.g., certain I/O, Object.wait without a timeout, or a synchronized block waiting on a lock that never becomes available), the thread may continue past the timeout point.@After/@AfterEach method.@Test(timeout = 100)) and JUnit 5 (@Timeout(100)) and observe:
TestTimeoutException relative to the after‑hook log (if any).
Thread.sleep with a blocking I/O that ignores interruption, note any extra delay before the exception appears.
@TestFactory returning a stream of DynamicTest instances; the timeout behaves per invocation.If you are using a custom TestWatcher or RunListener and need to know exactly whether testFinished is called **before** or **after** the TestTimeoutException is caught by your test‑class try/catch, let me know which hook you are inspecting so I can confirm the ordering for your specific setup.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.