skip to content

A developer adds @TestExecutionListeners with a single custom listener and suddenly @Autowired stops working in the test. Why, and how does mergeMode fix it?

level: seniorimportance: must knowfreq 45%

answer

  1. default mergeMode = REPLACE_DEFAULTS (the trap)
  2. explicit listeners nuke DI/Tx/Sql/DirtiesContext
  3. MERGE_WITH_DEFAULTS keeps defaults + orders by @Order
  4. null @Autowired => check @TestExecutionListeners
  5. inheritListeners default true

basics

~10 s

Declaring @TestExecutionListeners with explicit listeners REPLACES the defaults by default, so DependencyInjectionTestExecutionListener is gone and injection breaks. Set mergeMode = MERGE_WITH_DEFAULTS to keep the defaults and add yours.

solid answer

~40 s

The @TestExecutionListeners annotation has a mergeMode attribute whose default is MergeMode.REPLACE_DEFAULTS. So the moment you list any listeners explicitly, Spring throws away the auto-registered default set — including DependencyInjectionTestExecutionListener, TransactionalTestExecutionListener, SqlScriptsTestExecutionListener, and DirtiesContextTestExecutionListener. That's why @Autowired, @Transactional, and @Sql all silently stop working. The fix is mergeMode = MergeMode.MERGE_WITH_DEFAULTS, which merges your listeners into the default set. When merging, Spring orders all listeners by their Ordered value / @Order, so your custom listener slots into the correct position rather than always running last. There's also inheritListeners (default true) controlling whether subclasses inherit listeners declared on superclasses. REPLACE_DEFAULTS is only appropriate when you deliberately want a minimal, fully-controlled listener chain.

go deeper

for a junior

Know that mergeMode exists and defaults replace, breaking injection.

for a middle

Choose MERGE_WITH_DEFAULTS to keep DI/Tx/Sql working.

for a senior

Explain ordering during merge and diagnose null @Autowired quickly.

for a principal

Weigh replace vs merge, inheritListeners, and Boot slice interactions.

## The trap `@org.springframework.test.context.TestExecutionListeners` lets you declare which listeners run. Its key attribute: ```java MergeMode mergeMode() default MergeMode.REPLACE_DEFAULTS; ``` The **default is `REPLACE_DEFAULTS`**. This is the single most common gotcha with this annotation: as soon as you write `@TestExecutionListeners(MyListener.class)`, you have *replaced* the entire default chain. The defaults that normally come from `spring.factories` — `DependencyInjectionTestExecutionListener`, `DirtiesContextTestExecutionListener`, `TransactionalTestExecutionListener`, `SqlScriptsTestExecutionListener`, `ServletTestExecutionListener`, event listeners, etc. — are no longer registered. Symptoms: `@Autowired` fields are `null`, `@Transactional` tests actually commit (no rollback listener), `@Sql` scripts silently don't run. ## The fix: MERGE_WITH_DEFAULTS ```java @TestExecutionListeners( listeners = MyCustomTestExecutionListener.class, mergeMode = MergeMode.MERGE_WITH_DEFAULTS ) ``` `MERGE_WITH_DEFAULTS` tells Spring: keep the default listeners and add mine. When merging, Spring does **not** just append; it sorts the combined set by each listener's `getOrder()` / `@Order` value (lower runs earlier in `before*` phases). Duplicate listener types are de-duplicated. So a custom listener with `@Order(2500)` will run *after* `DependencyInjectionTestExecutionListener` (order 2000) but *before* `DirtiesContextTestExecutionListener` (order 3000) — you get precise positioning. With `REPLACE_DEFAULTS`, ordering among the listeners you list is still honoured, but nothing else is present. ## inheritListeners Separately, `inheritListeners()` (default `true`) controls superclass inheritance. If a base test class declares listeners and a subclass declares more with `inheritListeners = true`, the subclass's listeners are appended to the inherited ones (then ordered). Set `false` to shadow the parent's declaration. ## When to use REPLACE_DEFAULTS Rarely. Valid cases: a highly specialised test base that intentionally runs without DI/transaction machinery, or a framework author building a bespoke chain. In application code, almost always prefer `MERGE_WITH_DEFAULTS` so you extend rather than cripple the standard behaviour. ## Boot note Spring Boot's slice annotations (`@DataJpaTest`, `@WebMvcTest`, etc.) and `@SpringBootTest` already merge in Boot-specific listeners (e.g. Mockito reset). If you replace defaults you can also lose those. Prefer merge. ## Diagnosis tip If injection mysteriously breaks after someone adds a listener, immediately check for a `@TestExecutionListeners` without `mergeMode = MERGE_WITH_DEFAULTS`.

  • When you use MERGE_WITH_DEFAULTS, does your listener always run last?
    No — Spring sorts the merged set by Ordered/@Order value, so your listener runs in its ordered position relative to the defaults, not necessarily last.
  • What is a legitimate reason to keep the default REPLACE_DEFAULTS behaviour?
    When you deliberately want a minimal, fully controlled listener chain with none of the standard DI/transaction/SQL machinery — rare, mostly framework-level.
  • How do subclasses interact with listeners declared on a superclass?
    Controlled by inheritListeners (default true), which appends inherited listeners; set false to ignore the parent's declaration.

saying these in an interview costs you the question

  • Believing @TestExecutionListeners adds to the defaults by default
  • Thinking a merged custom listener always executes last
  • Confusing mergeMode with inheritListeners

context