Close

JUnit 5 - ExtensionContext - Accessing Configuration Parameters

[Last Updated: Jan 5, 2026]

The JUnit 5 extension model provides access to external configuration parameters through the ExtensionContext interface. This enables extensions to read configuration values from various sources like system properties, configuration files, or environment variables, making extensions configurable without code changes.

Need for Configuration Parameters

Extensions often need to be configurable for different environments or use cases. Hardcoding values limits reusability, while configuration parameters allow extensions to adapt to different testing scenarios. External configuration enables runtime customization without modifying extension code.

ExtensionContext Methods for Configuration Parameters

Optional<String> getConfigurationParameter(String key)

Retrieves a configuration parameter value as a String by its key. Returns Optional.empty() if the parameter is not found. This is the basic method for accessing string-based configuration.

<T> Optional<T> getConfigurationParameter(String key,
                                          Function<? super String, ? extends @Nullable T> transformer)

Retrieves a configuration parameter and applies a transformation function to convert it to the desired type. Useful for parsing strings into numbers, booleans, enums, or custom types. Returns Optional.empty() if transformation fails.

Configuration Sources

Configuration parameters can be set through various sources with this priority order:

  1. JVM System Properties: Standard -Dkey=value (e.g., -Dretry.maxAttempts=3)
  2. Configuration Files: junit-platform.properties in classpath with simple key=value format

Example: Configurable Retry Extension

This example demonstrates a configurable retry extension that reads its behavior from external configuration. The extension reads maximum retry attempts, delay between retries, and retryable exception patterns from configuration parameters, showing how extensions can be customized without code changes.

Extension Implementation

package com.logicbig.example;

import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

public class ConfigurableRetryExtension implements BeforeEachCallback, AfterTestExecutionCallback {

    private static final String MAX_ATTEMPTS_KEY = "retry.maxAttempts";
    private static final String DELAY_KEY = "retry.delay";
    private static final String RETRY_ON_KEY = "retry.retryOn";

    private static final String ATTEMPT_KEY = "retry-attempt";
    private static final Namespace NAMESPACE =
            Namespace.create(ConfigurableRetryExtension.class);

    @Override
    public void beforeEach(ExtensionContext context) {
        // Initialize attempt counter
        context.getStore(NAMESPACE).put(ATTEMPT_KEY, 1);

        // Read and log configuration
        readConfiguration(context);
    }

    @Override
    public void afterTestExecution(ExtensionContext context) {
        Optional<Throwable> executionException = context.getExecutionException();

        if (executionException.isPresent()) {
            Throwable exception = executionException.get();

            // Read configuration values
            int maxAttempts = readMaxAttempts(context);
            long delay = readDelay(context);
            List<String> retryOnExceptions = readRetryOnExceptions(context);

            // Check if exception should trigger retry
            if (shouldRetry(exception, retryOnExceptions)) {
                int currentAttempt = context.getStore(NAMESPACE).get(ATTEMPT_KEY, Integer.class);

                if (currentAttempt < maxAttempts) {
                    System.out.println("[RETRY] Attempt " + currentAttempt +
                                               " failed with " + exception.getClass().getSimpleName());
                    System.out.println("[RETRY] Retrying (max " + maxAttempts +
                                               " attempts, delay " + delay + "ms)");

                    // Increment attempt counter
                    context.getStore(NAMESPACE).put(ATTEMPT_KEY, currentAttempt + 1);

                    // Simulate retry delay
                    try {
                        Thread.sleep(delay);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }

                    // In real implementation, you would re-execute the test
                    System.out.println("[RETRY] Would retry test here");
                } else {
                    System.out.println("[RETRY] Max attempts (" + maxAttempts + ") reached");
                }
            } else {
                System.out.println("[RETRY] Exception " + exception.getClass().getSimpleName() +
                                           " not in retry list");
            }
        }
    }

    private void readConfiguration(ExtensionContext context) {
        System.out.println("[CONFIG] Reading configuration parameters:");

        // Read with transformation
        Optional<Integer> maxAttempts = context.getConfigurationParameter(MAX_ATTEMPTS_KEY, Integer::valueOf);
        maxAttempts.ifPresent(value ->
                                      System.out.println("  " + MAX_ATTEMPTS_KEY + " = " + value)
        );

        Optional<Long> delay = context.getConfigurationParameter(DELAY_KEY, Long::valueOf);
        delay.ifPresent(value ->
                                System.out.println("  " + DELAY_KEY + " = " + value + "ms")
        );

        Optional<List<String>> retryOn =
                context.getConfigurationParameter(RETRY_ON_KEY,
                                                  value -> Arrays.asList(value.split(",")));
        retryOn.ifPresent(list ->
                                  System.out.println("  " + RETRY_ON_KEY + " = " + list)
        );

        // Show missing configuration
        if (maxAttempts.isEmpty()) {
            System.out.println("  " + MAX_ATTEMPTS_KEY + " = (not set, using default: 2)");
        }
        if (delay.isEmpty()) {
            System.out.println("  " + DELAY_KEY + " = (not set, using default: 100ms)");
        }
        if (retryOn.isEmpty()) {
            System.out.println("  " + RETRY_ON_KEY + " = (not set, using default: [])");
        }
    }

    private int readMaxAttempts(ExtensionContext context) {
        return context.getConfigurationParameter(MAX_ATTEMPTS_KEY, Integer::valueOf)
                      .orElse(2); // Default value
    }

    private long readDelay(ExtensionContext context) {
        return context.getConfigurationParameter(DELAY_KEY, Long::valueOf)
                      .orElseThrow(() -> new RuntimeException(
                              "'%s' not provided".formatted(DELAY_KEY)));
    }

    private List<String> readRetryOnExceptions(ExtensionContext context) {
        return context.getConfigurationParameter(RETRY_ON_KEY,
                                                 value -> Arrays.asList(value.split(",")))
                      .orElse(Collections.emptyList());
    }

    private boolean shouldRetry(Throwable exception,
                                List<String> retryOnExceptions) {
        String exceptionName = exception.getClass().getSimpleName();
        return retryOnExceptions.stream()
                                .anyMatch(exceptionName::contains);
    }
}

Test Class Using the Extension

package com.logicbig.example;

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

@ExtendWith(ConfigurableRetryExtension.class)
public class ConfigurationParameterTest {

    @Test
    void successfulTest() {
        System.out.println("[TEST] Executing successful test");
        // This test should pass
        int result = 10 + 5;
        System.out.println("[TEST] Result: " + result);
    }

    @Test
    void testWithTimeoutException() {
        System.out.println("[TEST] Executing test that throws TimeoutException");
        // Simulate timeout
        throw new RuntimeException("Connection timeout after 5000ms");
    }

    @Test
    void testWithNetworkException() {
        System.out.println("[TEST] Executing test that throws NetworkException");
        // Simulate network error
        throw new RuntimeException("Network unreachable");
    }

    @Test
    void testWithValidationException() {
        System.out.println("[TEST] Executing test that throws ValidationException");
        // This exception type is not in retry configuration
        throw new RuntimeException("Validation failed: invalid input");
    }
}

Output

$ mvn test -Dtest=ConfigurationParameterTest -Dretry.maxAttempts=3 -Dretry.delay=200 -Dretry.retryOn=TimeoutException,NetworkException
[INFO] Scanning for projects...
[INFO]
[INFO] --< com.logicbig.example:junit-5-extension-configuration-parameters >---
[INFO] Building junit-5-extension-configuration-parameters 1.0-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ junit-5-extension-configuration-parameters ---
[INFO] skip non existing resourceDirectory D:\example-projects\junit-5\junit-5-extension-context\junit-5-extension-configuration-parameters\src\main\resources
[INFO]
[INFO] --- compiler:3.11.0:compile (default-compile) @ junit-5-extension-configuration-parameters ---
[INFO] No sources to compile
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ junit-5-extension-configuration-parameters ---
[INFO] Copying 1 resource from src\test\resources to target\test-classes
[INFO]
[INFO] --- compiler:3.11.0:testCompile (default-testCompile) @ junit-5-extension-configuration-parameters ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- surefire:3.5.0:test (default-test) @ junit-5-extension-configuration-parameters ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[CONFIG] Reading configuration parameters:
retry.maxAttempts = 3
retry.delay = 200ms
retry.retryOn = [TimeoutException, NetworkException]
[TEST] Executing test that throws TimeoutException
[RETRY] Exception RuntimeException not in retry list
[CONFIG] Reading configuration parameters:
retry.maxAttempts = 3
retry.delay = 200ms
retry.retryOn = [TimeoutException, NetworkException]
[TEST] Executing successful test
[TEST] Result: 15
[CONFIG] Reading configuration parameters:
retry.maxAttempts = 3
retry.delay = 200ms
retry.retryOn = [TimeoutException, NetworkException]
[TEST] Executing test that throws ValidationException
[RETRY] Exception RuntimeException not in retry list
[CONFIG] Reading configuration parameters:
retry.maxAttempts = 3
retry.delay = 200ms
retry.retryOn = [TimeoutException, NetworkException]
[TEST] Executing test that throws NetworkException
[RETRY] Exception RuntimeException not in retry list
[INFO] +--com.logicbig.example.ConfigurationParameterTest - 0.120 ss
[INFO] | +-- [XX] testWithTimeoutException - 0.065 ss
[INFO] | +-- [OK] successfulTest - 0.019 ss
[INFO] | +-- [XX] testWithValidationException - 0.002 ss
[INFO] | '-- [XX] testWithNetworkException - 0.003 ss
[INFO]
[INFO] Results:
[INFO]
[ERROR] Errors:
[ERROR] ConfigurationParameterTest.testWithNetworkException:28 Runtime Network unreachable
[ERROR] ConfigurationParameterTest.testWithTimeoutException:21 Runtime Connection timeout after 5000ms
[ERROR] ConfigurationParameterTest.testWithValidationException:35 Runtime Validation failed: invalid input
[INFO]
[ERROR] Tests run: 4, Failures: 0, Errors: 3, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.353 s
[INFO] Finished at: 2026-01-04T23:15:39+08:00
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.5.0:test (default-test) on project junit-5-extension-configuration-parameters:
[ERROR]
[ERROR] Please refer to D:\example-projects\junit-5\junit-5-extension-context\junit-5-extension-configuration-parameters\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

The output demonstrates how the ConfigurableRetryExtension successfully reads and applies configuration parameters.

Providing Configuration Parameters

Configuration parameters can be provided through multiple sources. In the example above, parameters are set via Maven command line:

mvn test -Dtest=ConfigurationParameterTest \
  -Dretry.maxAttempts=3 \
  -Dretry.delay=200 \
  -Dretry.retryOn=TimeoutException,NetworkException

Alternative Configuration Methods

Configuration parameters can also be set through:

1. JUnit Platform Properties File

Create src/test/resources/junit-platform.properties:

retry.maxAttempts=3
retry.delay=200
retry.retryOn=TimeoutException,NetworkException

2. System Properties in pom.xml

<plugin>
       <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
              ......... 
              <configuration> 
                    <systemPropertyVariables>
                        <retry.maxAttempts>3</retry.maxAttempts>
                        <retry.delay>200</retry.delay>
                        <retry.retryOn>TimeoutException,NetworkException</retry.retryOn>
                    </systemPropertyVariables>
               ..................

Example Project

Dependencies and Technologies Used:

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

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

    • 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 - Extension Context Accessing Configuration Parameters Select All Download
  • junit-5-extension-configuration-parameters
    • src
      • test
        • java
          • com
            • logicbig
              • example
                • ConfigurationParameterTest.java
          • resources

    See Also