skip to content

What is ApplicationContextInitializer and how do you use the `initializers` attribute of @ContextConfiguration?

level: seniorimportance: should knowfreq 35%

answer

  1. initialize(ctx) runs pre-refresh
  2. mutate Environment / add PropertySource / register beans
  3. @ContextConfiguration(initializers = ...)
  4. part of MergedContextConfiguration cache key
  5. Testcontainers dynamic port injection; @Order for multiples

basics

~20 s

An ApplicationContextInitializer is a callback that runs against the ConfigurableApplicationContext just before it's refreshed. You register it via @ContextConfiguration(initializers = ...) to programmatically tweak the context — add property sources, activate profiles, or register bean definitions.

solid answer

~40 s

`ApplicationContextInitializer<C extends ConfigurableApplicationContext>` has one method, `initialize(C ctx)`, invoked after the context is created but **before** `refresh()` — so before bean definitions are instantiated. In tests you register one or more via `@ContextConfiguration(initializers = MyInitializer.class)`. Because it runs pre-refresh, it's the right place to mutate the `Environment` (add `PropertySource`s, activate profiles), register additional `BeanDefinition`s, or add `BeanFactoryPostProcessor`s. Testcontainers-style setups use it to inject dynamically-allocated ports/URLs into the environment before beans wire them. Multiple initializers run in `@Order`/`Ordered` sequence. The initializer set is part of the `MergedContextConfiguration`, so it participates in the context cache key — different initializers mean a different cached context. `inheritInitializers` (default true) controls whether superclass initializers are merged.

code

java · 20 lines
java
class DynamicPropsInitializer
        implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext ctx) {
        // runs BEFORE refresh: safe to add property sources / register beans
        Map<String, Object> props = Map.of(
                "app.feature.enabled", "true",
                "app.remote.url", TestBroker.startAndGetUrl());
        ctx.getEnvironment().getPropertySources()
           .addFirst(new MapPropertySource("test-dynamic", props));
    }
}

@ExtendWith(SpringExtension.class)
@ContextConfiguration(
        classes = AppConfig.class,
        initializers = DynamicPropsInitializer.class)
class FeatureFlagTest {
    @Value("${app.feature.enabled}") boolean enabled;
}

go deeper

for a junior

Know it's a hook that can tweak the context before it starts, registered via the initializers attribute.

for a middle

Explain the pre-refresh timing and a concrete use like adding a property source for a dynamic value.

for a senior

Cover ordering, inheritInitializers, cache-key impact, and Testcontainers/dynamic-config patterns.

for a principal

Contrast with @TestPropertySource/@DynamicPropertySource, discuss generic-type pitfalls and how SpringApplication's initializer hook maps to tests.

## What it is `org.springframework.context.ApplicationContextInitializer<C extends ConfigurableApplicationContext>` is a functional interface with a single method: ```java void initialize(C applicationContext); ``` Spring calls it **after the `ApplicationContext` instance is created but before `refresh()` is invoked** — i.e., after the bean *definitions/config* are known but **before** singletons are instantiated. That timing is the whole point: you can still influence the context wholesale. ## What you can do inside it Because you receive the `ConfigurableApplicationContext` pre-refresh, you can: - **Mutate the `Environment`**: `ctx.getEnvironment().getPropertySources().addFirst(new MapPropertySource(...))` to inject config; or `getEnvironment().setActiveProfiles(...)`. - **Register bean definitions** programmatically (via the context's `BeanDefinitionRegistry`). - **Register `BeanFactoryPostProcessor`s / `ApplicationListener`s** before refresh. - Historically the mechanism `SpringApplication` uses to apply initializers listed in `spring.factories`. You must **not** call `refresh()` yourself — the framework does that. ## Registering in tests `@ContextConfiguration` exposes an `initializers` attribute: ```java @ContextConfiguration(classes = AppConfig.class, initializers = PropsInitializer.class) ``` You can list several; they execute in order determined by `Ordered`/`@Order` (or `PriorityOrdered`). The `initializers` set feeds into the `MergedContextConfiguration`, so **it is part of the context cache key** — two tests differing only by initializer get **separate** cached contexts. ## inheritInitializers `@ContextConfiguration(inheritInitializers = ...)` (default `true`) controls whether a subclass test **merges** initializers declared on a superclass test. Set `false` to replace rather than accumulate. ## Canonical use case: Testcontainers / dynamic config Before `@DynamicPropertySource` existed, the standard pattern to feed a container's **runtime-assigned** host/port into Spring was an initializer: ```java new MapPropertySource("testcontainers", Map.of("spring.datasource.url", container.getJdbcUrl())) ``` added to the environment pre-refresh so `@Value`/`@ConfigurationProperties` see it. It still works and is more flexible than `@DynamicPropertySource` for registering beans, not just properties. ## Type parameter gotcha The generic `C` must be compatible with the actual context type. If you write `ApplicationContextInitializer<GenericWebApplicationContext>` but the test builds a non-web `GenericApplicationContext`, the framework will fail to apply it / throw at runtime. Prefer `ApplicationContextInitializer<ConfigurableApplicationContext>` unless you specifically need web APIs. ## Ordering & multiplicity - Multiple initializers -> sorted by `AnnotationAwareOrderComparator` (respects `@Order`, `Ordered`, `PriorityOrdered`). - No guaranteed order without explicit ordering annotations beyond declaration order handling — declare `@Order` when order matters. ## vs alternatives - **`@TestPropertySource`** — declarative properties, but static; can't compute values at runtime. - **`@DynamicPropertySource`** — runtime property values via a static method + `DynamicPropertyRegistry`, but properties only. - **`ApplicationContextInitializer`** — full programmatic access (properties **and** bean definitions/post-processors), at the cost of more code. ## Interview framing Bring it up when asked how to inject runtime-computed configuration, how Testcontainers wires ports pre-Boot 2.2.6, or how `SpringApplication`'s initializer hook maps onto the test framework. Emphasize the **pre-refresh timing** and the **cache-key** consequence.

  • Why does an ApplicationContextInitializer run before refresh(), and why does that matter?
    Before refresh() no singletons are instantiated and bean definitions can still be added/modified and the Environment mutated. That's the only safe window to inject property sources or register beans that other beans will wire during refresh.
  • How does declaring an initializer affect the test context cache?
    The initializers set is part of MergedContextConfiguration, so it's included in the cache key; two otherwise-identical tests with different initializers get separate cached contexts.
  • When would you choose an initializer over @DynamicPropertySource?
    When you need to register bean definitions or post-processors, not just properties. @DynamicPropertySource only feeds computed property values; an initializer gives full pre-refresh programmatic access.

saying these in an interview costs you the question

  • Claiming initialize() runs after refresh or after beans are created
  • Calling refresh() manually inside the initializer
  • Forgetting that initializers participate in the context cache key
  • Using a web-specific generic type on a non-web context

context