Close

JUnit 5 - Introduction to Jupiter Extension Model

[Last Updated: Jan 1, 2026]

The Jupiter extension model allows developers to inject custom behavior into the test lifecycle. Unlike JUnit 4's Rules and Runners, the Extension API is modular and allows multiple extensions to be composed together using the @ExtendWith annotation. This annotation is used on a test class (or test method) to register one or more extensions to be applied.

Java source and doc

Definition of ExtendWith

Version: 6.0.1
 package org.junit.jupiter.api.extension;
 @Target({ ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 @Inherited
 @Repeatable(Extensions.class)
 @API(status = STABLE, since = "5.0")
 public @interface ExtendWith {
     Class<? extends Extension>[] value(); 1
 }
1An array of one or more Extension classes to register.

What is Extension?

Extension is a marker interface that identifies a class as a JUnit Jupiter extension. By itself, it does not declare any methods.

Widely-used third-party @ExtendWith extensions

Let's see some famous JUnit Jupiter extensions to get an idea what they are capable of. These third-party extensions demonstrate the real-world power and flexibility of JUnit 5's extension model, showing how various testing concerns can be cleanly separated and reused across projects.

Spring Boot Test

@ExtendWith(SpringExtension.class)
@SpringBootTest
class SpringIntegrationTest {
    // Automatically loads Spring context
    // Provides dependency injection
}

Mockito

@ExtendWith(MockitoExtension.class)
class MockitoTest {
    @Mock
    private Service service;
    
    @InjectMocks
    private Controller controller;
}

Testcontainers

@Testcontainers
@ExtendWith(TestcontainersExtension.class)
class DatabaseTest {
    @Container
    static PostgreSQLContainer<?> postgres = 
        new PostgreSQLContainer<>("postgres:15");
}

Example: Building a Custom Extension

One of the most common type of Extension is ParameterResolver. By implementing the ParameterResolver interface, an extension can dynamically provide arguments to test methods at runtime.

Definition of ParameterResolver

Version: 6.0.1
 package org.junit.jupiter.api.extension;
 @API(status = STABLE, since = "5.0")
 public interface ParameterResolver extends TestInstantiationAwareExtension {
     boolean supportsParameter(ParameterContext parameterContext, 1
                               ExtensionContext extensionContext)
                               throws ParameterResolutionException;
     Object resolveParameter(ParameterContext parameterContext, 2
                             ExtensionContext extensionContext)
                             throws ParameterResolutionException;
 }
1Determine if this resolver supports resolution of an argument for the Parameter in the supplied ParameterContext for the supplied ExtensionContext.
2Resolve an argument for the Parameter in the supplied ParameterContext for the supplied ExtensionContext.

As seen above, ParameterResolver extends TestInstantiationAwareExtension which extends Extension interface.

Both of the above methods in ParameterResolver interface have a parameter ExtensionContext, the purpose of this parameter is to give extensions access to details about the currently executing test, including the test class, test method, display name, tags, and test instance.

In this example, we will create a custom extension, PageInjectionExtension. It will look for parameters annotated with @Uri and injects a mock Page object initialized with that specific path.

The Custom Extension Implementation

package com.logicbig.example;

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

public class PageInjectionExtension implements ParameterResolver {
    @Override
    public boolean supportsParameter(ParameterContext pc, ExtensionContext ec) {
        return pc.isAnnotated(Uri.class) && pc.getParameter().getType() == Page.class;
    }

    @Override
    public Object resolveParameter(ParameterContext pc, ExtensionContext ec) {
        String path = pc.findAnnotation(Uri.class).get().value();
        //simulate finding Page from the backend
        return new Page(path, "Content of " + path);
    }
}

The custom annotation

package com.logicbig.example;

import java.lang.annotation.*;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Uri {
    String value();
}

The Custom Parameter Type

public record Page(String path, String content) {}

Test Class

package com.logicbig.example;

import org.junit.jupiter.api.Test;

import org.junit.jupiter.api.extension.ExtendWith;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

@ExtendWith(PageInjectionExtension.class)
public class PageInjectionTest {

    @Test
    void testPageLoad(@Uri("/example/my-page.html") Page page) {
        System.out.println("Page: " + page);
        assertNotNull(page);
        assertEquals("/example/my-page.html", page.path());
        assertNotNull(page.content());
    }
}

Output

$ mvn test -Dtest=PageInjectionTest
[INFO] Scanning for projects...
[INFO]
[INFO] -----< com.logicbig.example:junit-5-extension-parameter-resolver >------
[INFO] Building junit-5-extension-parameter-resolver 1.0-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ junit-5-extension-parameter-resolver ---
[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-extension-parameter-resolver\src\main\resources
[INFO]
[INFO] --- compiler:3.13.0:compile (default-compile) @ junit-5-extension-parameter-resolver ---
[INFO] No sources to compile
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ junit-5-extension-parameter-resolver ---
[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-extension-parameter-resolver\src\test\resources
[INFO]
[INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ junit-5-extension-parameter-resolver ---
[INFO] Nothing to compile - all classes are up to date.
[INFO]
[INFO] --- surefire:3.5.0:test (default-test) @ junit-5-extension-parameter-resolver ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
Page: Page[path=/example/my-page.html, content=Content of /example/my-page.html]
[INFO] +--com.logicbig.example.PageInjectionTest - 0.094 ss
[INFO] | '-- [OK] testPageLoad(Page) - 0.067 ss
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.324 s
[INFO] Finished at: 2025-12-29T15:15:58+08:00
[INFO] ------------------------------------------------------------------------

The output confirms that the ParameterResolver successfully intercepted the test method signature. By reading the @Uri value, the extension instantiated the Page object with the correct path, allowing the test to verify content specific to that URI without manual setup boilerplate.

Example Project

Dependencies and Technologies Used:

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

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

    • 5.1.1
    • 5.2.0
    • 5.3.0
    • 5.3.1
    • 5.3.2
    • 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 - Jupiter Extension ParameterResolver Select All Download
  • junit-5-extension-parameter-resolver
    • src
      • test
        • java
          • com
            • logicbig
              • example
                • PageInjectionTest.java

    See Also