skip to content

What can you customize on a SpringApplication BEFORE the context refreshes, and how do you hook in?

level: principalimportance: should knowfreq 30%

answer

  1. setDefaultProperties = lowest-precedence fallback
  2. initializers run in prepareContext, before refresh
  3. early events need addListeners/spring.factories not @Bean
  4. setLazyInitialization / bannerMode / additionalProfiles
  5. beans-needed work waits for refresh / runners

basics

~20 s

Before refresh you can set defaults on the SpringApplication instance: default properties, additional profiles, banner mode, web type, lazy initialization, and register ApplicationContextInitializers and ApplicationListeners. These run during the early bootstrap phases, before beans are instantiated.

solid answer

~40 s

Between constructing a SpringApplication and refresh, there's a window to influence bootstrap. On the instance you can call setDefaultProperties(...) (lowest-precedence fallbacks), setAdditionalProfiles(...), setBannerMode(...), setWebApplicationType(...), setLazyInitialization(true), setAllowBeanDefinitionOverriding(...), addPrimarySources(...), and register addInitializers(ApplicationContextInitializer...) and addListeners(ApplicationListener...). Initializers run in ApplicationContext prepare, after the context is created but before refresh — perfect for programmatically registering bean definitions or tweaking the Environment. Listeners react to early events (ApplicationStartingEvent, ApplicationEnvironmentPreparedEvent) that fire before any bean exists. You can also register these declaratively in META-INF/spring.factories. This is how you set safe defaults a user can still override, adjust the Environment early, enable global lazy init, or fail fast. Anything requiring a bean must instead wait for refresh (e.g. a @Bean, SmartInitializingSingleton, or ContextRefreshedEvent).

code

java · 22 lines
java
public static void main(String[] args) {
    SpringApplication app = new SpringApplication(App.class);

    // Overridable fallback defaults (lowest precedence)
    app.setDefaultProperties(Map.of("server.port", "8080", "app.feature.x", "true"));
    app.setAdditionalProfiles("metrics");
    app.setBannerMode(Banner.Mode.OFF);
    app.setLazyInitialization(true);

    // Runs after context creation, BEFORE refresh — env/bean-def tweaks, no beans yet
    app.addInitializers((ConfigurableApplicationContext ctx) -> {
        ctx.getEnvironment().getPropertySources().addFirst(
            new MapPropertySource("forced", Map.of("app.mode", "boot")));
    });

    // Catches the earliest events (before any @Bean listener would)
    app.addListeners((ApplicationListener<ApplicationEnvironmentPreparedEvent>) e ->
        System.out.println("Env prepared, active profiles: "
            + String.join(",", e.getEnvironment().getActiveProfiles())));

    app.run(args);
}

go deeper

for a junior

May know you can turn the banner off or set a default port.

for a middle

Should know default properties, additional profiles, banner mode, and lazy init setters.

for a senior

Should place initializers/early listeners in the bootstrap timeline and know their capabilities.

for a principal

Should design bootstrap policy — overridable defaults, spring.factories registration, precedence, fail-fast — and know exactly what can/can't run pre-refresh.

## The pre-refresh window `SpringApplication.run()` proceeds through ordered phases; **context refresh** is late in the sequence (it instantiates singletons and starts the server). Everything you configure on the `SpringApplication` object, or via early listeners/initializers, happens **before** refresh — so it runs before any of your beans exist. This is the extension surface for shaping how the whole app boots. ## Instance setters (call before run) On a `SpringApplication` (or the equivalent `SpringApplicationBuilder` method): - **`setDefaultProperties(Map/Properties)`** — adds a `defaultProperties` property source at the **lowest precedence**, so these are fallback values any real config (env var, `application.yml`, CLI arg) overrides. Use for baked-in sensible defaults. - **`setAdditionalProfiles(String...)`** — activates extra profiles on top of whatever `spring.profiles.active` resolves to. - **`setBannerMode(Banner.Mode)`** — `CONSOLE`, `LOG`, or `OFF`; `setBanner(Banner)` for a custom banner. - **`setWebApplicationType(WebApplicationType)`** — override classpath deduction (SERVLET/REACTIVE/NONE). - **`setLazyInitialization(true)`** — global lazy bean init (equivalent to `spring.main.lazy-initialization=true`); beans are created on first use rather than eagerly at refresh. - **`setAllowBeanDefinitionOverriding(boolean)`** — allow a later bean definition to override an earlier same-named one (off by default in Boot 2.1+). - **`setRegisterShutdownHook(boolean)`**, **`setLogStartupInfo(boolean)`**, **`setHeadless(boolean)`**, **`addPrimarySources(...)`**, **`setDefaultProperties`**, etc. ## ApplicationContextInitializer `addInitializers(ApplicationContextInitializer<ConfigurableApplicationContext>...)`. These are invoked during the **prepareContext** phase — **after** the context object is created but **before** `refresh()`. Because the context (and its `BeanDefinitionRegistry` and `Environment`) exists but no beans are instantiated, an initializer can: - register additional bean definitions programmatically, - add or reorder `PropertySource`s on the `Environment`, - set active profiles conditionally, - register `BeanFactoryPostProcessor`s. Initializers can be ordered with `@Order`/`Ordered`. They can also be declared in `META-INF/spring.factories` under `org.springframework.context.ApplicationContextInitializer` so they apply without code changes. ## ApplicationListener and early events `addListeners(ApplicationListener...)` registers listeners that receive the **early SpringApplicationEvents** fired before the context is usable: - `ApplicationStartingEvent` — very first, before Environment. - `ApplicationEnvironmentPreparedEvent` — Environment ready (you can still mutate property sources/profiles here). - `ApplicationContextInitializedEvent`, `ApplicationPreparedEvent` — context created/prepared, pre-refresh. - `ApplicationStartedEvent`, `ApplicationReadyEvent`, `ApplicationFailedEvent` — refresh-time/after. These early events are delivered by `SpringApplicationRunListener`s, **not** through the normal context multicaster (the context isn't refreshed yet), which is why you register them on the `SpringApplication` (or `spring.factories`) rather than as `@Bean`s. A `@Bean`-based `ApplicationListener` only starts receiving events from `ContextRefreshedEvent` onward. ## When to use each - **Default properties** — ship overridable defaults (e.g. a default port or feature flag). - **Initializer** — mutate the context/Environment or register infra bean definitions before refresh; the standard way to influence bootstrap without a bean. - **Early listener** — observe/fail-fast on startup and environment events before beans exist. - **Lazy init / overriding / banner** — global bootstrap policy. ## Gotchas - Anything needing an actual **bean** cannot run pre-refresh — use a `@Bean`, `SmartInitializingSingleton`, `@EventListener`/`ContextRefreshedEvent`, or an `ApplicationRunner`/`CommandLineRunner` instead. - `setDefaultProperties` is **lowest** precedence — don't use it to force a value; real config overrides it. To force, use a higher-precedence source. - Early listeners registered as `@Bean` **miss** `ApplicationStartingEvent`/`EnvironmentPreparedEvent`; register via `addListeners` or `spring.factories` to catch them. - Global lazy init can hide startup failures until first use and delay first-request latency — use deliberately.

  • Why can't an @Bean ApplicationListener catch ApplicationStartingEvent?
    That event fires before the context is created and refreshed, so no beans exist yet. Only listeners registered on the SpringApplication (addListeners) or via spring.factories receive it.
  • What's the precedence of setDefaultProperties versus an environment variable?
    Default properties are the lowest-precedence source, so an environment variable (or application.yml, or CLI arg) always overrides them. They're fallbacks, not overrides.
  • You need to register a bean definition conditionally before refresh — what hook?
    An ApplicationContextInitializer: it runs after the context is created but before refresh, with access to the BeanDefinitionRegistry and Environment.

saying these in an interview costs you the question

  • Thinking setDefaultProperties forces/overrides real config
  • Expecting @Bean listeners to receive pre-refresh startup events
  • Believing initializers run after refresh / after beans are instantiated
  • Confusing ApplicationContextInitializer with ApplicationRunner (which runs after refresh)

context