Close

JUnit 5 - Test Result Processing with TestWatcher Extension

[Last Updated: Jan 1, 2026]

The TestWatcher interface allows you to monitor the results of test execution. Unlike other callbacks that trigger based on the execution phase, TestWatcher provides specific methods for different outcomes: success, failure, abortion, or disabled tests. It is essentially a 'listener' for the final status of a test.

Java source and doc

Definition of TestWatcher

Version: 6.0.1
 package org.junit.jupiter.api.extension;
 @API(status = STABLE, since = "5.7")
 public interface TestWatcher extends Extension {
     default void testDisabled(ExtensionContext context, 1
                               Optional<String> reason);
     default void testSuccessful(ExtensionContext context); 2
     default void testAborted(ExtensionContext context, 3
                              @Nullable Throwable cause);
     default void testFailed(ExtensionContext context, 4
                             @Nullable Throwable cause);
 }
1Invoked after a disabled test has been skipped.
2Invoked after a test has completed successfully.
3Invoked after a test has been aborted.
4Invoked after a test has failed.

Example

In this example, we implement a ResultLoggerExtension. This extension captures the outcome of each test and prints a specific summary. In a real-world scenario, you might use the testFailed method to trigger a database rollback or save a diagnostic log file.

package com.logicbig.example;

import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestWatcher;
import java.util.Optional;

public class ResultLoggerExtension implements TestWatcher {

    @Override
    public void testSuccessful(ExtensionContext context) {
        System.out.println("✅ SUCCESS: " + context.getDisplayName());
    }

    @Override
    public void testFailed(ExtensionContext context, Throwable cause) {
        System.err.println("� FAILED: " + context.getDisplayName() + " | Reason: " + cause.getMessage());
    }

    @Override
    public void testAborted(ExtensionContext context, Throwable cause) {
        System.out.println("⚠� ABORTED: " + context.getDisplayName());
    }

    @Override
    public void testDisabled(ExtensionContext context, Optional<String> reason) {
        System.out.println("🚫 DISABLED: " + context.getDisplayName() + " | Reason: " + reason.orElse("none"));
    }
}

Test Class

package com.logicbig.example;

import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(ResultLoggerExtension.class)
public class TestWatcherExample {

    @Test
    void successfulTest() {
        Assertions.assertTrue(true);
    }

    @Test
    void failingTest() {
        Assertions.fail("Explicit failure to trigger TestWatcher");
    }

    @Test
    @Disabled("Demonstrating disabled callback")
    void disabledTest() {
    }
}

Output

$ mvn test -Dtest=TestWatcherExample
[INFO] Scanning for projects...
[INFO]
[INFO] -------------< com.logicbig.example:junit-5-test-watcher >--------------
[INFO] Building junit-5-test-watcher 1.0-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ junit-5-test-watcher ---
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\example-projects\junit-5\junit-5-extensions\junit-5-test-watcher\src\main\resources
[INFO]
[INFO] --- compiler:3.13.0:compile (default-compile) @ junit-5-test-watcher ---
[INFO] No sources to compile
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ junit-5-test-watcher ---
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\example-projects\junit-5\junit-5-extensions\junit-5-test-watcher\src\test\resources
[INFO]
[INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ junit-5-test-watcher ---
[INFO] Nothing to compile - all classes are up to date.
[INFO]
[INFO] --- surefire:3.2.5:test (default-test) @ junit-5-test-watcher ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.logicbig.example.TestWatcherExample
✅ SUCCESS: successfulTest()
🚫 DISABLED: disabledTest() | Reason: Demonstrating disabled callback
[ERROR] Tests run: 3, Failures: 1, Errors: 0, Skipped: 1, Time elapsed: 0.097 s <<< FAILURE! -- in com.logicbig.example.TestWatcherExample
[ERROR] com.logicbig.example.TestWatcherExample.failingTest -- Time elapsed: 0.017 s <<< FAILURE!
org.opentest4j.AssertionFailedError: Explicit failure to trigger TestWatcher
at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:42)
at org.junit.jupiter.api.Assertions.fail(Assertions.java:143)
at com.logicbig.example.TestWatcherExample.failingTest(TestWatcherExample.java:16)

[INFO]
[INFO] Results:
[INFO]
[ERROR] Failures:
[ERROR] TestWatcherExample.failingTest:16 Explicit failure to trigger TestWatcher
[INFO]
[ERROR] Tests run: 3, Failures: 1, Errors: 0, Skipped: 1
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.364 s
[INFO] Finished at: 2026-01-01T08:00:38+08:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.2.5:test (default-test) on project junit-5-test-watcher: There are test failures.
[ERROR]
[ERROR] Please refer to D:\example-projects\junit-5\junit-5-extensions\junit-5-test-watcher\target\surefire-reports for the individual test results.
[ERROR] Please refer to dump files (if any exist) [date].dump, [date]-jvmRun[N].dump and [date].dumpstream.
[ERROR] -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
�?� FAILED: failingTest() | Reason: Explicit failure to trigger TestWatcher

The console output highlights how the extension reacts differently based on the test's success or failure. Notice that testSuccessful was called for the passing test, while testFailed captured the details of the assertion error in the failing test. This capability makes TestWatcher the standard choice for building custom reporting logic within the JUnit 5 ecosystem.

Example Project

Dependencies and Technologies Used:

  • junit-jupiter-engine 6.0.1 (Module "junit-jupiter-engine" of JUnit)
     Version Compatibility: 5.4.0 - 6.0.1Version List
    ×

    Version compatibilities of junit-jupiter-engine with this example:

    • 5.4.0
    • 5.4.1
    • 5.4.2
    • 5.5.0
    • 5.5.1
    • 5.5.2
    • 5.6.0
    • 5.6.1
    • 5.6.2
    • 5.6.3
    • 5.7.0
    • 5.7.1
    • 5.7.2
    • 5.8.0
    • 5.8.1
    • 5.8.2
    • 5.9.0
    • 5.9.1
    • 5.9.2
    • 5.9.3
    • 5.10.0
    • 5.10.1
    • 5.10.2
    • 5.10.3
    • 5.10.4
    • 5.10.5
    • 5.11.0
    • 5.11.1
    • 5.11.2
    • 5.11.3
    • 5.11.4
    • 5.12.0
    • 5.12.1
    • 5.12.2
    • 5.13.0
    • 5.13.1
    • 5.13.2
    • 5.13.3
    • 5.13.4
    • 5.14.0
    • 5.14.1
    • 6.0.0
    • 6.0.1

    Versions in green have been tested.

  • JDK 25
  • Maven 3.9.11

JUnit 5 - TestWatcher Select All Download
  • junit-5-test-watcher
    • src
      • test
        • java
          • com
            • logicbig
              • example
                • TestWatcherExample.java

    See Also