skip to content

Registering Extensions

The three ways to wire an extension in and when each takes effect. Interviewers contrast declarative @ExtendWith with programmatic @RegisterExtension and ServiceLoader auto-detection.

on this pageshow

questions

4

In JUnit 5 (Jupiter), how do you plug a reusable extension such as the Mockito or Spring test integration into a test, and which declaration sites are valid for the @ExtendWith annotation?

level: juniorimportance: must knowfreq 60%

answer

  1. @ExtendWith = declarative, class/method/field/parameter/meta-annotation
  2. No-arg constructor → cannot configure
  3. Inherited by subclasses and @Nested
  4. Repeatable + array; duplicates deduped
  5. Stackable, unlike JUnit 4 @RunWith

basics

~20 s

Declaratively, with @ExtendWith(SomeExtension.class). It can go on a test class, on a single test method, on a field or parameter, or on your own meta-annotation. JUnit builds the extension with its no-arg constructor, and class-level registration is inherited by subclasses and @Nested classes.

solid answer

~50 s

JUnit 5 replaced JUnit 4's runners and rules with one **Extension** API, and the everyday way to register one is declarative `@ExtendWith`. - On a **class**: `@ExtendWith(MockitoExtension.class)` applies to every test in it, and is inherited by subclasses and `@Nested` classes. - On a **test method**: applies to that method only. - On a **field or parameter** (Jupiter 5.8+): useful for a `ParameterResolver` you want for one argument. - It is repeatable and takes an array: `@ExtendWith({A.class, B.class})`. - It composes: put `@ExtendWith` on your own annotation and you have a `@SpringBootTest`-style meta-annotation. Unlike JUnit 4's `@RunWith`, you can stack as many extensions as you want. The limitation is that Jupiter instantiates the extension through its **no-arg constructor**, so you cannot configure the instance. When the extension needs constructor arguments or a builder, switch to programmatic registration with `@RegisterExtension` on a field. Declaring the same extension type twice in one context is harmless — Jupiter registers each implementation once.

code

java · 15 lines
java
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {

    @Test
    @ExtendWith(TimingExtension.class)
    void slowPathIsMeasured() { }
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith({MockitoExtension.class, TimingExtension.class})
@interface UnitTest { }

@UnitTest
class PaymentServiceTest { }

go deeper

for a junior

Know that @ExtendWith(X.class) on the class is how you turn on Mockito or Spring in a JUnit 5 test, and that you can list several.

for a middle

Add the other targets (method, field/parameter, meta-annotation), inheritance to subclasses and @Nested, and the no-arg-constructor limitation that pushes you to @RegisterExtension.

for a senior

Explain the registry model — deduplication, registration order, before/after callbacks nesting like try/finally — and design shared meta-annotations for a codebase's test archetypes.

for a principal

Frame it as the team's testing contract: a small set of curated composed annotations (@UnitTest, @IntegrationTest) so test setup is uniform and swappable, rather than every class hand-rolling extension lists.

## One plug-in mechanism instead of two JUnit 4 had two extension points that did not compose. `@RunWith(SomeRunner.class)` was powerful but exclusive: one runner per class, so you could not have the Spring runner and the Mockito runner at the same time. `@Rule`/`@ClassRule` composed but could only wrap statement execution. Jupiter merges both ideas into the `org.junit.jupiter.api.extension.Extension` marker interface, which sub-interfaces such as `BeforeEachCallback`, `ParameterResolver`, `TestInstancePostProcessor` and `ExecutionCondition` extend. An extension is just a class implementing one or more of those callback interfaces; registering it means telling Jupiter to add it to the extension registry for some scope. ## Declarative registration with @ExtendWith `@ExtendWith` is the declarative path — you name a **class**, Jupiter creates the instance for you. Valid targets: 1. **Test class** — the extension participates in every test in that class. Because Jupiter honours annotation inheritance, an abstract `IntegrationTestBase` annotated with `@ExtendWith` passes it to every subclass, and `@Nested` inner classes inherit from the enclosing class. 2. **Test method** — scoped to that one method; class-level callbacks such as `BeforeAllCallback` cannot fire meaningfully there because the class-level phase has already happened. 3. **Field or parameter** (since Jupiter 5.8) — mostly for `ParameterResolver`s that should only apply to one injection point. 4. **Another annotation** (meta-annotation) — this is how `@SpringBootTest` and `@DataJpaTest` work: they are ordinary annotations that themselves carry `@ExtendWith(SpringExtension.class)`, so the user never types `@ExtendWith`. `@ExtendWith` is `@Repeatable` and its value is an array, so `@ExtendWith({A.class, B.class})` and two stacked annotations are equivalent. ## Instantiation and deduplication Jupiter instantiates a declaratively registered extension via its **default (no-arg) constructor**. That is the whole reason `@RegisterExtension` exists: with `@ExtendWith` there is no place to pass a port number, a builder result, or a preconfigured client. The extension class must be concrete, and it must not be `private`. Jupiter also **deduplicates**: the same extension implementation registered more than once inside one registry hierarchy is only applied once. So a base class with `@ExtendWith(SpringExtension.class)` plus a subclass repeating it does not double the callbacks. This is why meta-annotations can be layered freely. ## Ordering, briefly Extensions are registered in a deterministic order: inherited/class-level declarations first (outermost class down), then the order in which `@ExtendWith` annotations appear, and programmatically registered extensions after them. "Before" callbacks then run in registration order and "after" callbacks in reverse — an onion, like nested try/finally blocks. Relying on the exact interleaving of unrelated extensions is fragile; if order matters, use `@Order` on programmatically registered fields. ## Choosing between the two styles Use `@ExtendWith` when the extension needs no configuration — Mockito, Spring, your own logging or clock-freezing extension with sensible defaults. Use `@RegisterExtension` when you must build the instance, or when the test body needs to talk to it (for example asking a WireMock extension for its randomly assigned port). Both end up in the same registry; the annotation only decides who constructs the object.

  • Why can you stack several extensions in JUnit 5 when JUnit 4 allowed only one @RunWith?
    A JUnit 4 runner owned the whole execution lifecycle of the class, so two runners would each want to drive the run and could not be combined. Jupiter inverts that: the engine drives execution and extensions only implement narrow callback interfaces the engine invokes at defined points. Because callbacks compose (they nest like try/finally), any number can be registered.
  • What happens if the same extension class is registered on both a base class and its subclass?
    Nothing bad — Jupiter deduplicates extension implementations within a registry hierarchy, so it is applied once. This is what makes layered meta-annotations safe: @SpringBootTest on a subclass of a base class that already pulls in SpringExtension does not run Spring's callbacks twice.
  • How would you write a reusable annotation that pulls in several extensions plus a tag?
    Create your own annotation with @Retention(RUNTIME) and the right @Target, then annotate it with @ExtendWith({...}) and @Tag("integration"). Jupiter resolves meta-annotations recursively, so applying your annotation to a test class registers everything it carries. This is exactly the mechanism behind @SpringBootTest and @DataJpaTest.

@ExtendWith is like listing plugin class names in a config file: the framework news up each one for you with defaults. @RegisterExtension is handing the framework an object you built yourself.

saying these in an interview costs you the question

  • Claiming you can only have one extension per class, confusing it with JUnit 4's @RunWith
  • Thinking @ExtendWith lets you pass constructor arguments or configuration to the extension
  • Believing extensions are not inherited, so every subclass must repeat the annotation
  • Saying JUnit 5 still supports @Rule natively (it does not; only the junit-jupiter-migrationsupport module offers limited rule support)

context

open as a page

When would you register a JUnit 5 Jupiter extension programmatically with @RegisterExtension on a field instead of declaring it with @ExtendWith, and what rules apply to that field?

level: middleimportance: should knowfreq 38%

basics

~20 s

Use @RegisterExtension when you must build the extension instance yourself — constructor arguments or a builder — or when the test needs to query it (for example a randomly assigned port). The field must be non-private, of an Extension type, and non-null when JUnit reads it.

open as a page

A JUnit 5 extension registered with @RegisterExtension behaves differently depending on whether the field is static or an instance field. What is the difference, and how do you decide which to use?

level: middleimportance: should knowfreq 30%

basics

~20 s

A static field is registered before class-level setup, so the extension can implement class-level callbacks such as BeforeAllCallback, AfterAllCallback and TestInstancePostProcessor. An instance field is registered only after the test instance is created, so class-level callbacks are not honoured — only per-test ones like BeforeEachCallback.

open as a page

JUnit 5 can pick up extensions from the classpath automatically via the ServiceLoader. How is that turned on, what does an extension author have to publish, and why is it usually not the default choice?

level: seniorimportance: nice to knowfreq 16%

basics

~20 s

Set the configuration parameter junit.jupiter.extensions.autodetection.enabled=true (in junit-platform.properties, as a system property, or in the discovery request). The extension JAR must declare its class in META-INF/services/org.junit.jupiter.api.extension.Extension. It then applies to every test, which is why it is opt-in and off by default.

open as a page