or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

admin-jmx.mdansi-support.mdaot-native-image.mdapplication-info.mdavailability.mdbootstrap.mdbootstrapping.mdbuilder.mdcloud-platform.mdconfiguration-annotations.mdconfiguration-data.mdconfiguration-properties.mdconversion.mddiagnostics.mdenvironment-property-sources.mdindex.mdjson-support.mdlifecycle-events.mdlogging.mdorigin-tracking.mdresource-loading.mdretry-support.mdssl-tls.mdstartup-metrics.mdsupport.mdsystem-utilities.mdtask-execution.mdthreading.mdutilities.mdvalidation.mdweb-support.md
tile.json

lifecycle-events.mddocs/

Spring Boot Application Lifecycle Events

Quick Reference

This documentation has been enhanced for AI coding agents with comprehensive examples, complete API signatures, thread safety notes, error handling patterns, and production-ready usage patterns.

Key Lifecycle Events

Event ClassWhen PublishedThread SafetyApplicationContext AvailableKey Use Cases
ApplicationStartingEventVery early, before Environment/ContextThread-safe (immutable)NoVery early initialization, bootstrap registry setup
ApplicationEnvironmentPreparedEventEnvironment readyThread-safe (immutable)NoAdd/modify property sources, Environment validation
ApplicationContextInitializedEventContext created, initializers calledThread-safe (immutable)Yes (unprepared)Context configuration before bean loading
ApplicationPreparedEventContext prepared, beans not instantiatedThread-safe (immutable)Yes (prepared)Final bean definition modifications
ApplicationStartedEventContext refreshed, before runnersThread-safe (immutable)Yes (refreshed)Access initialized beans, startup tasks
ApplicationReadyEventApplication fully readyThread-safe (immutable)Yes (running)Trigger readiness indicators, start scheduled tasks
ApplicationFailedEventStartup failure occurredThread-safe (immutable)MaybeError handling, cleanup, failure notifications

Bootstrap Context Components

ComponentPurposeThread SafetyKey Use Cases
BootstrapRegistryRegister instances for early accessThread-safeRegister expensive objects before ApplicationContext
BootstrapContextRead-only access to registryThread-safeRetrieve registered instances
ConfigurableBootstrapContextRead-write accessThread-safeFull bootstrap context access
BootstrapRegistryInitializerInitialize bootstrap registryN/AProgrammatic registration
BootstrapContextClosedEventBootstrap context closure notificationThread-safe (immutable)Migrate instances to ApplicationContext

SpringApplicationRunListener

MethodWhen CalledEnvironment AvailableContext Available
starting()Very firstNoNo
environmentPrepared()Environment readyYesNo
contextPrepared()Context createdYesYes (unprepared)
contextLoaded()Bean definitions loadedYesYes (unrefreshed)
started()Context refreshedYesYes (refreshed)
ready()Fully readyYesYes (running)
failed()On failureDependsDepends

This document provides comprehensive coverage of Spring Boot's application lifecycle event system, including all lifecycle event classes, the SpringApplicationRunListener interface, bootstrap context support, and related components.

Table of Contents

Overview

Spring Boot provides a comprehensive lifecycle event system that allows applications to hook into various stages of the application startup and shutdown process. The lifecycle follows this sequence:

  1. ApplicationStartingEvent - Very early, before Environment or ApplicationContext
  2. ApplicationEnvironmentPreparedEvent - Environment ready for inspection
  3. ApplicationContextInitializedEvent - Context created, initializers called
  4. ApplicationPreparedEvent - Context fully prepared, bean definitions loaded
  5. ApplicationStartedEvent - Context refreshed, before runners
  6. ApplicationReadyEvent - Application ready to service requests
  7. ApplicationFailedEvent - Published on failure (can occur at any stage)

All lifecycle events extend from SpringApplicationEvent, which itself extends Spring's ApplicationEvent.

SpringApplicationEvent Base Class

The base class for all Spring Boot application lifecycle events.

Type Definition

package org.springframework.boot.context.event;

import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationEvent;

/**
 * Base class for {@link ApplicationEvent} related to a {@link SpringApplication}.
 * Provides access to the SpringApplication instance and command-line arguments
 * that were passed during application startup.
 *
 * @author Phillip Webb
 * @since 1.0.0
 */
@SuppressWarnings("serial")
public abstract class SpringApplicationEvent extends ApplicationEvent {

    private final String[] args;

    /**
     * Create a new SpringApplicationEvent.
     * @param application the SpringApplication instance (source of the event)
     * @param args the command-line arguments
     */
    public SpringApplicationEvent(SpringApplication application, String[] args) {
        super(application);
        this.args = args;
    }

    /**
     * Return the SpringApplication associated with the event.
     * @return the SpringApplication instance
     */
    public SpringApplication getSpringApplication() {
        return (SpringApplication) getSource();
    }

    /**
     * Return the command-line arguments.
     * @return the arguments array
     */
    public final String[] getArgs() {
        return this.args;
    }
}

Key Features

  • Extends Spring's standard ApplicationEvent
  • Provides access to the SpringApplication instance
  • Stores command-line arguments passed to the application
  • All lifecycle events inherit these capabilities

Lifecycle Event Classes

ApplicationStartingEvent

Published as early as conceivably possible when a SpringApplication has been started. This is before the Environment or ApplicationContext is available, but after ApplicationListeners have been registered.

Type Definition

package org.springframework.boot.context.event;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.bootstrap.ConfigurableBootstrapContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.Environment;

/**
 * Event published as early as conceivably possible as soon as a {@link SpringApplication}
 * has been started - before the {@link Environment} or {@link ApplicationContext} is
 * available, but after the {@link ApplicationListener}s have been registered.
 *
 * The source of the event is the {@link SpringApplication} itself, but beware of using
 * its internal state too much at this early stage since it might be modified later in
 * the lifecycle.
 *
 * @author Phillip Webb
 * @author Madhura Bhave
 * @since 1.5.0
 */
@SuppressWarnings("serial")
public class ApplicationStartingEvent extends SpringApplicationEvent {

    private final ConfigurableBootstrapContext bootstrapContext;

    /**
     * Create a new {@link ApplicationStartingEvent} instance.
     * @param bootstrapContext the bootstrap context
     * @param application the current application
     * @param args the arguments the application is running with
     */
    public ApplicationStartingEvent(ConfigurableBootstrapContext bootstrapContext,
                                   SpringApplication application,
                                   String[] args) {
        super(application, args);
        this.bootstrapContext = bootstrapContext;
    }

    /**
     * Return the bootstrap context.
     * @return the bootstrap context
     * @since 2.4.0
     */
    public ConfigurableBootstrapContext getBootstrapContext() {
        return this.bootstrapContext;
    }
}

Use Cases

  • Very early initialization tasks
  • Registering instances in the bootstrap registry
  • Setting up logging before the Environment is available
  • Installing custom property sources

ApplicationEnvironmentPreparedEvent

Published when a SpringApplication is starting up and the Environment is first available for inspection and modification.

Type Definition

package org.springframework.boot.context.event;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.bootstrap.ConfigurableBootstrapContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;

/**
 * Event published when a {@link SpringApplication} is starting up and the
 * {@link Environment} is first available for inspection and modification.
 *
 * This event is published after the Environment has been prepared but before
 * the ApplicationContext has been created. It's the ideal time to customize
 * the Environment.
 *
 * @author Dave Syer
 * @since 1.0.0
 */
@SuppressWarnings("serial")
public class ApplicationEnvironmentPreparedEvent extends SpringApplicationEvent {

    private final ConfigurableBootstrapContext bootstrapContext;
    private final ConfigurableEnvironment environment;

    /**
     * Create a new {@link ApplicationEnvironmentPreparedEvent} instance.
     * @param bootstrapContext the bootstrap context
     * @param application the current application
     * @param args the arguments the application is running with
     * @param environment the environment that was just created
     */
    public ApplicationEnvironmentPreparedEvent(ConfigurableBootstrapContext bootstrapContext,
                                              SpringApplication application,
                                              String[] args,
                                              ConfigurableEnvironment environment) {
        super(application, args);
        this.bootstrapContext = bootstrapContext;
        this.environment = environment;
    }

    /**
     * Return the bootstrap context.
     * @return the bootstrap context
     * @since 2.4.0
     */
    public ConfigurableBootstrapContext getBootstrapContext() {
        return this.bootstrapContext;
    }

    /**
     * Return the environment.
     * @return the environment
     */
    public ConfigurableEnvironment getEnvironment() {
        return this.environment;
    }
}

Use Cases

  • Adding custom property sources to the Environment
  • Modifying or validating configuration properties
  • Setting up environment-specific configurations
  • Implementing EnvironmentPostProcessor logic

ApplicationContextInitializedEvent

Published when a SpringApplication is starting up and the ApplicationContext is prepared. ApplicationContextInitializers have been called but before any bean definitions are loaded.

Type Definition

package org.springframework.boot.context.event;

import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * Event published when a {@link SpringApplication} is starting up and the
 * {@link ApplicationContext} is prepared and ApplicationContextInitializers have been
 * called but before any bean definitions are loaded.
 *
 * This provides an opportunity to perform operations on the ApplicationContext
 * after it has been initialized but before beans are defined.
 *
 * @author Artsiom Yudovin
 * @since 2.1.0
 */
@SuppressWarnings("serial")
public class ApplicationContextInitializedEvent extends SpringApplicationEvent {

    private final ConfigurableApplicationContext context;

    /**
     * Create a new {@link ApplicationContextInitializedEvent} instance.
     * @param application the current application
     * @param args the arguments the application is running with
     * @param context the context that has been initialized
     */
    public ApplicationContextInitializedEvent(SpringApplication application,
                                             String[] args,
                                             ConfigurableApplicationContext context) {
        super(application, args);
        this.context = context;
    }

    /**
     * Return the application context.
     * @return the context
     */
    public ConfigurableApplicationContext getApplicationContext() {
        return this.context;
    }
}

Use Cases

  • Performing operations after ApplicationContextInitializers
  • Setting up context-level configurations
  • Registering custom BeanFactoryPostProcessors

ApplicationPreparedEvent

Published when a SpringApplication is starting up and the ApplicationContext is fully prepared but not refreshed. Bean definitions are loaded and the Environment is ready for use.

Type Definition

package org.springframework.boot.context.event;

import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;

/**
 * Event published when a {@link SpringApplication} is starting up and the
 * {@link ApplicationContext} is fully prepared but not refreshed. The bean definitions
 * will be loaded and the {@link Environment} is ready for use at this stage.
 *
 * This is the last opportunity to perform operations before the ApplicationContext
 * is refreshed and beans are instantiated.
 *
 * @author Dave Syer
 * @since 1.0.0
 */
@SuppressWarnings("serial")
public class ApplicationPreparedEvent extends SpringApplicationEvent {

    private final ConfigurableApplicationContext context;

    /**
     * Create a new {@link ApplicationPreparedEvent} instance.
     * @param application the current application
     * @param args the arguments the application is running with
     * @param context the ApplicationContext about to be refreshed
     */
    public ApplicationPreparedEvent(SpringApplication application,
                                   String[] args,
                                   ConfigurableApplicationContext context) {
        super(application, args);
        this.context = context;
    }

    /**
     * Return the application context.
     * @return the context
     */
    public ConfigurableApplicationContext getApplicationContext() {
        return this.context;
    }
}

Use Cases

  • Last-minute bean definition modifications
  • Adding custom BeanFactoryPostProcessors
  • Registering additional beans programmatically
  • Setting up context-specific configurations

ApplicationStartedEvent

Published once the application context has been refreshed but before any ApplicationRunner and CommandLineRunner have been called.

Type Definition

package org.springframework.boot.context.event;

import java.time.Duration;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * Event published once the application context has been refreshed but before any
 * {@link ApplicationRunner application} and {@link CommandLineRunner command line}
 * runners have been called.
 *
 * This event indicates that the application is fully started and all beans are
 * instantiated, but custom runners haven't executed yet.
 *
 * @author Andy Wilkinson
 * @since 2.0.0
 */
@SuppressWarnings("serial")
public class ApplicationStartedEvent extends SpringApplicationEvent {

    private final ConfigurableApplicationContext context;
    private final @Nullable Duration timeTaken;

    /**
     * Create a new {@link ApplicationStartedEvent} instance.
     * @param application the current application
     * @param args the arguments the application is running with
     * @param context the context that was being created
     * @param timeTaken the time taken to start the application, or null if unknown
     * @since 2.6.0
     */
    public ApplicationStartedEvent(SpringApplication application,
                                  String[] args,
                                  ConfigurableApplicationContext context,
                                  @Nullable Duration timeTaken) {
        super(application, args);
        this.context = context;
        this.timeTaken = timeTaken;
    }

    /**
     * Return the application context.
     * @return the context
     */
    public ConfigurableApplicationContext getApplicationContext() {
        return this.context;
    }

    /**
     * Return the time taken to start the application, or {@code null} if unknown.
     * @return the startup time
     * @since 2.6.0
     */
    public @Nullable Duration getTimeTaken() {
        return this.timeTaken;
    }
}

Use Cases

  • Performing operations after context refresh
  • Accessing fully initialized beans
  • Setting up background tasks
  • Logging startup metrics

ApplicationReadyEvent

Published as late as conceivably possible to indicate that the application is ready to service requests. The source of the event is the SpringApplication itself.

Type Definition

package org.springframework.boot.context.event;

import java.time.Duration;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * Event published as late as conceivably possible to indicate that the application is
 * ready to service requests. The source of the event is the {@link SpringApplication}
 * itself, but beware of modifying its internal state since all initialization steps will
 * have been completed by then.
 *
 * This is the final event in the successful startup sequence, published after all
 * CommandLineRunners and ApplicationRunners have been called.
 *
 * @author Stephane Nicoll
 * @author Chris Bono
 * @since 1.3.0
 * @see ApplicationFailedEvent
 */
@SuppressWarnings("serial")
public class ApplicationReadyEvent extends SpringApplicationEvent {

    private final ConfigurableApplicationContext context;
    private final @Nullable Duration timeTaken;

    /**
     * Create a new {@link ApplicationReadyEvent} instance.
     * @param application the current application
     * @param args the arguments the application is running with
     * @param context the context that was being created
     * @param timeTaken the time taken to get the application ready to service requests,
     *                  or null if unknown
     * @since 2.6.0
     */
    public ApplicationReadyEvent(SpringApplication application,
                                String[] args,
                                ConfigurableApplicationContext context,
                                @Nullable Duration timeTaken) {
        super(application, args);
        this.context = context;
        this.timeTaken = timeTaken;
    }

    /**
     * Return the application context.
     * @return the context
     */
    public ConfigurableApplicationContext getApplicationContext() {
        return this.context;
    }

    /**
     * Return the time taken for the application to be ready to service requests,
     * or {@code null} if unknown.
     * @return the time taken to be ready to service requests
     * @since 2.6.0
     */
    public @Nullable Duration getTimeTaken() {
        return this.timeTaken;
    }
}

Use Cases

  • Indicating the application is fully started and ready
  • Publishing availability state changes
  • Starting scheduled tasks
  • Performing final initialization operations
  • Logging "application ready" messages

ApplicationFailedEvent

Published by a SpringApplication when it fails to start. This event can be published at any point during the startup sequence if a failure occurs.

Type Definition

package org.springframework.boot.context.event;

import org.jspecify.annotations.Nullable;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * Event published by a {@link SpringApplication} when it fails to start.
 *
 * This event is published whenever a failure occurs during the application startup
 * process. The ApplicationContext may or may not be available depending on when
 * the failure occurred.
 *
 * @author Dave Syer
 * @since 1.0.0
 * @see ApplicationReadyEvent
 */
@SuppressWarnings("serial")
public class ApplicationFailedEvent extends SpringApplicationEvent {

    private final @Nullable ConfigurableApplicationContext context;
    private final Throwable exception;

    /**
     * Create a new {@link ApplicationFailedEvent} instance.
     * @param application the current application
     * @param args the arguments the application was running with
     * @param context the context that was being created (may be null if failure
     *                occurred before context creation)
     * @param exception the exception that caused the failure
     */
    public ApplicationFailedEvent(SpringApplication application,
                                 String[] args,
                                 @Nullable ConfigurableApplicationContext context,
                                 Throwable exception) {
        super(application, args);
        this.context = context;
        this.exception = exception;
    }

    /**
     * Return the application context.
     * @return the context or {@code null} if the failure occurred before
     *         context creation
     */
    public @Nullable ConfigurableApplicationContext getApplicationContext() {
        return this.context;
    }

    /**
     * Return the exception that caused the failure.
     * @return the exception
     */
    public Throwable getException() {
        return this.exception;
    }
}

Use Cases

  • Handling startup failures gracefully
  • Logging detailed error information
  • Performing cleanup operations
  • Sending failure notifications
  • Implementing custom failure analysis

SpringApplicationRunListener

Interface for listening to the SpringApplication run method lifecycle. Listeners are loaded via SpringFactoriesLoader and should declare a public constructor accepting a SpringApplication instance and String[] arguments.

Type Definition

package org.springframework.boot;

import java.time.Duration;
import org.jspecify.annotations.Nullable;
import org.springframework.boot.bootstrap.ConfigurableBootstrapContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.support.SpringFactoriesLoader;

/**
 * Listener for the {@link SpringApplication} {@code run} method.
 * {@link SpringApplicationRunListener}s are loaded through the
 * {@link SpringFactoriesLoader} and should declare a public constructor that accepts a
 * {@link SpringApplication} instance and a {@code String[]} of arguments. A new
 * {@link SpringApplicationRunListener} instance will be created for each run.
 *
 * This interface provides a lower-level alternative to listening for application events.
 * It's particularly useful when you need to perform actions that can't wait for the
 * ApplicationContext to be available.
 *
 * @author Phillip Webb
 * @author Dave Syer
 * @author Andy Wilkinson
 * @author Chris Bono
 * @since 1.0.0
 */
public interface SpringApplicationRunListener {

    /**
     * Called immediately when the run method has first started. Can be used for very
     * early initialization.
     *
     * At this point:
     * - The bootstrap context is available
     * - ApplicationListeners have been registered
     * - The Environment has not been created yet
     * - The ApplicationContext has not been created yet
     *
     * @param bootstrapContext the bootstrap context
     */
    default void starting(ConfigurableBootstrapContext bootstrapContext) {
    }

    /**
     * Called once the environment has been prepared, but before the
     * {@link ApplicationContext} has been created.
     *
     * At this point:
     * - The Environment is fully prepared and can be modified
     * - Property sources have been loaded
     * - Profiles have been activated
     * - The ApplicationContext has not been created yet
     *
     * @param bootstrapContext the bootstrap context
     * @param environment the environment
     */
    default void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
                                    ConfigurableEnvironment environment) {
    }

    /**
     * Called once the {@link ApplicationContext} has been created and prepared, but
     * before sources have been loaded.
     *
     * At this point:
     * - The ApplicationContext has been created
     * - ApplicationContextInitializers have been called
     * - Bean definitions have not been loaded yet
     *
     * @param context the application context
     */
    default void contextPrepared(ConfigurableApplicationContext context) {
    }

    /**
     * Called once the application context has been loaded but before it has been
     * refreshed.
     *
     * At this point:
     * - Bean definitions have been loaded
     * - The context has not been refreshed yet
     * - Beans have not been instantiated yet
     *
     * @param context the application context
     */
    default void contextLoaded(ConfigurableApplicationContext context) {
    }

    /**
     * The context has been refreshed and the application has started but
     * {@link CommandLineRunner CommandLineRunners} and {@link ApplicationRunner
     * ApplicationRunners} have not been called.
     *
     * At this point:
     * - The ApplicationContext has been refreshed
     * - All beans have been instantiated
     * - Runners have not been called yet
     *
     * @param context the application context
     * @param timeTaken the time taken to start the application or {@code null} if unknown
     * @since 2.6.0
     */
    default void started(ConfigurableApplicationContext context, @Nullable Duration timeTaken) {
    }

    /**
     * Called immediately before the run method finishes, when the application context has
     * been refreshed and all {@link CommandLineRunner CommandLineRunners} and
     * {@link ApplicationRunner ApplicationRunners} have been called.
     *
     * At this point:
     * - The application is fully started
     * - All runners have been executed
     * - The application is ready to service requests
     *
     * @param context the application context
     * @param timeTaken the time taken for the application to be ready or {@code null} if
     *                  unknown
     * @since 2.6.0
     */
    default void ready(ConfigurableApplicationContext context, @Nullable Duration timeTaken) {
    }

    /**
     * Called when a failure occurs when running the application.
     *
     * This method is called if any exception occurs during the application startup
     * process. The ApplicationContext may or may not be available depending on when
     * the failure occurred.
     *
     * @param context the application context or {@code null} if a failure occurred before
     *                the context was created
     * @param exception the failure
     * @since 2.0.0
     */
    default void failed(@Nullable ConfigurableApplicationContext context, Throwable exception) {
    }
}

Registration

To register a custom SpringApplicationRunListener, add it to META-INF/spring.factories:

org.springframework.boot.SpringApplicationRunListener=\
com.example.MyCustomRunListener

Bootstrap Context

The bootstrap context system provides a way to register and share expensive objects during the early stages of application startup, before the main ApplicationContext is available.

BootstrapRegistry

Interface for registering objects in the bootstrap context.

Type Definition

package org.springframework.boot.bootstrap;

import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;

/**
 * A simple object registry that is available during startup and {@link Environment}
 * post-processing up to the point that the {@link ApplicationContext} is prepared.
 *
 * Can be used to register instances that may be expensive to create, or need to be shared
 * before the {@link ApplicationContext} is available.
 *
 * The registry uses {@link Class} as a key, meaning that only a single instance of a
 * given type can be stored.
 *
 * The {@link #addCloseListener(ApplicationListener)} method can be used to add a listener
 * that can perform actions when {@link BootstrapContext} has been closed and the
 * {@link ApplicationContext} is fully prepared. For example, an instance may choose to
 * register itself as a regular Spring bean so that it is available for the application to
 * use.
 *
 * @author Phillip Webb
 * @since 4.0.0
 * @see BootstrapContext
 * @see ConfigurableBootstrapContext
 */
public interface BootstrapRegistry {

    /**
     * Register a specific type with the registry. If the specified type has already been
     * registered and has not been obtained as a {@link Scope#SINGLETON singleton}, it
     * will be replaced.
     *
     * @param <T> the instance type
     * @param type the instance type
     * @param instanceSupplier the instance supplier
     */
    <T> void register(Class<T> type, InstanceSupplier<T> instanceSupplier);

    /**
     * Register a specific type with the registry if one is not already present.
     *
     * @param <T> the instance type
     * @param type the instance type
     * @param instanceSupplier the instance supplier
     */
    <T> void registerIfAbsent(Class<T> type, InstanceSupplier<T> instanceSupplier);

    /**
     * Return if a registration exists for the given type.
     *
     * @param <T> the instance type
     * @param type the instance type
     * @return {@code true} if the type has already been registered
     */
    <T> boolean isRegistered(Class<T> type);

    /**
     * Return any existing {@link InstanceSupplier} for the given type.
     *
     * @param <T> the instance type
     * @param type the instance type
     * @return the registered {@link InstanceSupplier} or {@code null}
     */
    <T> @Nullable InstanceSupplier<T> getRegisteredInstanceSupplier(Class<T> type);

    /**
     * Add an {@link ApplicationListener} that will be called with a
     * {@link BootstrapContextClosedEvent} when the {@link BootstrapContext} is closed and
     * the {@link ApplicationContext} has been prepared.
     *
     * @param listener the listener to add
     */
    void addCloseListener(ApplicationListener<BootstrapContextClosedEvent> listener);

    /**
     * Supplier used to provide the actual instance when needed.
     *
     * @param <T> the instance type
     * @see Scope
     */
    @FunctionalInterface
    interface InstanceSupplier<T> {

        /**
         * Factory method used to create the instance when needed.
         *
         * @param context the {@link BootstrapContext} which may be used to obtain other
         *                bootstrap instances
         * @return the instance or {@code null}
         */
        @Nullable T get(BootstrapContext context);

        /**
         * Return the scope of the supplied instance.
         *
         * @return the scope (defaults to SINGLETON)
         */
        default Scope getScope() {
            return Scope.SINGLETON;
        }

        /**
         * Return a new {@link InstanceSupplier} with an updated {@link Scope}.
         *
         * @param scope the new scope
         * @return a new {@link InstanceSupplier} instance with the new scope
         */
        default InstanceSupplier<T> withScope(Scope scope) {
            Assert.notNull(scope, "'scope' must not be null");
            InstanceSupplier<T> parent = this;
            return new InstanceSupplier<>() {
                @Override
                public @Nullable T get(BootstrapContext context) {
                    return parent.get(context);
                }

                @Override
                public Scope getScope() {
                    return scope;
                }
            };
        }

        /**
         * Factory method that can be used to create an {@link InstanceSupplier} for a
         * given instance.
         *
         * @param <T> the instance type
         * @param instance the instance
         * @return a new {@link InstanceSupplier}
         */
        static <T> InstanceSupplier<T> of(@Nullable T instance) {
            return (registry) -> instance;
        }

        /**
         * Factory method that can be used to create an {@link InstanceSupplier} from a
         * {@link Supplier}.
         *
         * @param <T> the instance type
         * @param supplier the supplier that will provide the instance
         * @return a new {@link InstanceSupplier}
         */
        static <T> InstanceSupplier<T> from(@Nullable Supplier<T> supplier) {
            return (registry) -> (supplier != null) ? supplier.get() : null;
        }
    }

    /**
     * The scope of an instance.
     */
    enum Scope {

        /**
         * A singleton instance. The {@link InstanceSupplier} will be called only once and
         * the same instance will be returned each time.
         */
        SINGLETON,

        /**
         * A prototype instance. The {@link InstanceSupplier} will be called whenever an
         * instance is needed.
         */
        PROTOTYPE
    }
}

BootstrapContext

Interface providing read-only access to the bootstrap context.

Type Definition

package org.springframework.boot.bootstrap;

import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.Environment;

/**
 * A simple bootstrap context that is available during startup and {@link Environment}
 * post-processing up to the point that the {@link ApplicationContext} is prepared.
 *
 * Provides lazy access to singletons that may be expensive to create, or need to be
 * shared before the {@link ApplicationContext} is available.
 *
 * Instances are registered by type. The context may return {@code null} values when a
 * type has been registered but no value is actually supplied.
 *
 * @author Phillip Webb
 * @since 4.0.0
 * @see BootstrapRegistry
 */
public interface BootstrapContext {

    /**
     * Return an instance from the context if the type has been registered. The instance
     * will be created if it hasn't been accessed previously.
     *
     * @param <T> the instance type
     * @param type the instance type
     * @return the instance managed by the context, which may be {@code null}
     * @throws IllegalStateException if the type has not been registered
     */
    <T> @Nullable T get(Class<T> type) throws IllegalStateException;

    /**
     * Return an instance from the context if the type has been registered. The instance
     * will be created if it hasn't been accessed previously.
     *
     * @param <T> the instance type
     * @param type the instance type
     * @param other the instance to use if the type has not been registered
     * @return the instance, which may be {@code null}
     */
    <T> @Nullable T getOrElse(Class<T> type, @Nullable T other);

    /**
     * Return an instance from the context if the type has been registered. The instance
     * will be created if it hasn't been accessed previously.
     *
     * @param <T> the instance type
     * @param type the instance type
     * @param other a supplier for the instance to use if the type has not been registered
     * @return the instance, which may be {@code null}
     */
    <T> @Nullable T getOrElseSupply(Class<T> type, Supplier<@Nullable T> other);

    /**
     * Return an instance from the context if the type has been registered. The instance
     * will be created if it hasn't been accessed previously.
     *
     * @param <T> the instance type
     * @param <X> the exception to throw if the type is not registered
     * @param type the instance type
     * @param exceptionSupplier the supplier which will return the exception to be thrown
     * @return the instance managed by the context, which may be {@code null}
     * @throws X if the type has not been registered
     */
    <T, X extends Throwable> @Nullable T getOrElseThrow(Class<T> type,
                                                        Supplier<? extends X> exceptionSupplier)
            throws X;

    /**
     * Return if a registration exists for the given type.
     *
     * @param <T> the instance type
     * @param type the instance type
     * @return {@code true} if the type has already been registered
     */
    <T> boolean isRegistered(Class<T> type);
}

ConfigurableBootstrapContext

Combines BootstrapRegistry and BootstrapContext for full read-write access.

Type Definition

package org.springframework.boot.bootstrap;

/**
 * A {@link BootstrapContext} that also provides configuration methods through the
 * {@link BootstrapRegistry} interface.
 *
 * This interface combines the read capabilities of BootstrapContext with the write
 * capabilities of BootstrapRegistry, providing full access to the bootstrap context
 * during the early stages of application startup.
 *
 * @author Phillip Webb
 * @since 4.0.0
 * @see BootstrapRegistry
 * @see BootstrapContext
 * @see DefaultBootstrapContext
 */
public interface ConfigurableBootstrapContext extends BootstrapRegistry, BootstrapContext {
}

BootstrapRegistryInitializer

Callback interface for initializing a BootstrapRegistry before it is used.

Type Definition

package org.springframework.boot.bootstrap;

/**
 * Callback interface that can be used to initialize a {@link BootstrapRegistry} before it
 * is used.
 *
 * This interface is typically implemented by classes that need to register instances
 * in the bootstrap registry during the very early stages of application startup.
 * Implementations are loaded via SpringFactoriesLoader or can be added programmatically
 * to SpringApplication.
 *
 * @author Phillip Webb
 * @since 4.0.0
 * @see BootstrapRegistry
 */
@FunctionalInterface
public interface BootstrapRegistryInitializer {

    /**
     * Initialize the given {@link BootstrapRegistry} with any required registrations.
     *
     * This method is called very early in the application lifecycle, before the
     * Environment is prepared. Use it to register instances that may be needed
     * during environment preparation or context initialization.
     *
     * @param registry the registry to initialize
     */
    void initialize(BootstrapRegistry registry);
}

Registration

Implementations can be registered in META-INF/spring.factories:

org.springframework.boot.BootstrapRegistryInitializer=\
com.example.MyBootstrapRegistryInitializer

Or programmatically:

SpringApplication app = new SpringApplication(MyApplication.class);
app.addBootstrapRegistryInitializer(registry -> {
    // Register instances
});

BootstrapContextClosedEvent

Event published when the BootstrapContext is closed and the ApplicationContext has been prepared.

Type Definition

package org.springframework.boot.bootstrap;

import org.springframework.context.ApplicationEvent;
import org.springframework.context.ConfigurableApplicationContext;

/**
 * {@link ApplicationEvent} published by a {@link BootstrapContext} when it's closed.
 *
 * This event is published after the BootstrapContext has been closed and the
 * ApplicationContext is fully prepared. Listeners can use this event to migrate
 * instances from the bootstrap context to the application context, or to perform
 * cleanup operations.
 *
 * @author Phillip Webb
 * @since 4.0.0
 * @see BootstrapRegistry#addCloseListener(org.springframework.context.ApplicationListener)
 */
public class BootstrapContextClosedEvent extends ApplicationEvent {

    private final ConfigurableApplicationContext applicationContext;

    /**
     * Create a new {@link BootstrapContextClosedEvent} instance.
     *
     * @param source the BootstrapContext that was closed
     * @param applicationContext the prepared application context
     */
    BootstrapContextClosedEvent(BootstrapContext source,
                               ConfigurableApplicationContext applicationContext) {
        super(source);
        this.applicationContext = applicationContext;
    }

    /**
     * Return the {@link BootstrapContext} that was closed.
     *
     * @return the bootstrap context
     */
    public BootstrapContext getBootstrapContext() {
        return (BootstrapContext) this.source;
    }

    /**
     * Return the prepared application context.
     *
     * @return the application context
     */
    public ConfigurableApplicationContext getApplicationContext() {
        return this.applicationContext;
    }
}

Usage Examples

Listening to Lifecycle Events

Using @EventListener Annotation

import org.springframework.boot.context.event.*;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class ApplicationLifecycleListener {

    @EventListener
    public void onApplicationStarting(ApplicationStartingEvent event) {
        System.out.println("Application is starting...");
        System.out.println("Args: " + String.join(", ", event.getArgs()));
    }

    @EventListener
    public void onEnvironmentPrepared(ApplicationEnvironmentPreparedEvent event) {
        System.out.println("Environment prepared");
        String appName = event.getEnvironment()
                             .getProperty("spring.application.name");
        System.out.println("Application name: " + appName);
    }

    @EventListener
    public void onContextInitialized(ApplicationContextInitializedEvent event) {
        System.out.println("Context initialized, ID: "
            + event.getApplicationContext().getId());
    }

    @EventListener
    public void onApplicationPrepared(ApplicationPreparedEvent event) {
        System.out.println("Application context prepared");
        System.out.println("Bean count: "
            + event.getApplicationContext().getBeanDefinitionCount());
    }

    @EventListener
    public void onApplicationStarted(ApplicationStartedEvent event) {
        System.out.println("Application started");
        if (event.getTimeTaken() != null) {
            System.out.println("Startup time: "
                + event.getTimeTaken().toMillis() + "ms");
        }
    }

    @EventListener
    public void onApplicationReady(ApplicationReadyEvent event) {
        System.out.println("Application is ready to service requests");
        if (event.getTimeTaken() != null) {
            System.out.println("Total time: "
                + event.getTimeTaken().toMillis() + "ms");
        }
    }

    @EventListener
    public void onApplicationFailed(ApplicationFailedEvent event) {
        System.err.println("Application failed to start");
        System.err.println("Exception: " + event.getException().getMessage());
    }
}

Implementing ApplicationListener

import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class ApplicationReadyListener
        implements ApplicationListener<ApplicationReadyEvent> {

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        System.out.println("Application is ready!");

        // Access the application context
        var context = event.getApplicationContext();
        String[] beanNames = context.getBeanDefinitionNames();

        System.out.println("Total beans: " + beanNames.length);

        // Access startup time
        if (event.getTimeTaken() != null) {
            System.out.println("Ready in: "
                + event.getTimeTaken().toMillis() + "ms");
        }
    }
}

Early Listener (Before Context Creation)

For events that occur before the ApplicationContext is created, you must register the listener directly with SpringApplication:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationStartingEvent;

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(MyApplication.class);

        // Add listener for early events
        app.addListeners(event -> {
            if (event instanceof ApplicationStartingEvent) {
                System.out.println("Very early initialization");
            }
        });

        app.run(args);
    }
}

Custom SpringApplicationRunListener

Create a custom run listener to hook into all lifecycle stages:

import org.springframework.boot.*;
import org.springframework.boot.bootstrap.ConfigurableBootstrapContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import java.time.Duration;

public class CustomRunListener implements SpringApplicationRunListener {

    private final SpringApplication application;
    private final String[] args;

    // Required constructor
    public CustomRunListener(SpringApplication application, String[] args) {
        this.application = application;
        this.args = args;
    }

    @Override
    public void starting(ConfigurableBootstrapContext bootstrapContext) {
        System.out.println("==> Starting");
        // Register instances in bootstrap context if needed
        bootstrapContext.register(MyService.class,
            context -> new MyService());
    }

    @Override
    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
                                   ConfigurableEnvironment environment) {
        System.out.println("==> Environment prepared");
        // Add custom property sources
        String profile = environment.getProperty("spring.profiles.active");
        System.out.println("Active profile: " + profile);
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        System.out.println("==> Context prepared");
        // Perform context-level initialization
    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        System.out.println("==> Context loaded");
        // Bean definitions are loaded but not instantiated
    }

    @Override
    public void started(ConfigurableApplicationContext context, Duration timeTaken) {
        System.out.println("==> Started in " + timeTaken.toMillis() + "ms");
        // All beans are instantiated
    }

    @Override
    public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
        System.out.println("==> Ready in " + timeTaken.toMillis() + "ms");
        // Application is fully ready
    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        System.err.println("==> Failed: " + exception.getMessage());
        // Handle startup failure
    }
}

Register in META-INF/spring.factories:

org.springframework.boot.SpringApplicationRunListener=\
com.example.CustomRunListener

Bootstrap Registry Usage

Registering and Using Bootstrap Instances

import org.springframework.boot.BootstrapRegistryInitializer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.bootstrap.BootstrapRegistry;
import org.springframework.boot.context.event.ApplicationStartingEvent;

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(MyApplication.class);

        // Add bootstrap registry initializer
        app.addBootstrapRegistryInitializer(registry -> {
            // Register expensive service
            registry.register(DatabaseConnectionPool.class,
                context -> new DatabaseConnectionPool());

            // Register with prototype scope
            registry.register(RequestIdGenerator.class,
                BootstrapRegistry.InstanceSupplier
                    .from(() -> new RequestIdGenerator())
                    .withScope(BootstrapRegistry.Scope.PROTOTYPE));
        });

        app.run(args);
    }
}

Using Bootstrap Context in Listeners

import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationListener;

public class EnvironmentInitializer
        implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {

    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        // Access bootstrap context
        var bootstrapContext = event.getBootstrapContext();

        // Get registered instance
        DatabaseConnectionPool pool =
            bootstrapContext.get(DatabaseConnectionPool.class);

        // Use it to initialize environment
        if (pool != null) {
            event.getEnvironment().getPropertySources()
                .addLast(new DatabasePropertySource(pool));
        }
    }
}

Migrating Bootstrap Instances to ApplicationContext

import org.springframework.boot.BootstrapRegistryInitializer;
import org.springframework.boot.bootstrap.BootstrapRegistry;
import org.springframework.context.support.GenericApplicationContext;

public class MyBootstrapInitializer implements BootstrapRegistryInitializer {

    @Override
    public void initialize(BootstrapRegistry registry) {
        // Register instance
        registry.register(ExpensiveService.class,
            context -> new ExpensiveService());

        // Add close listener to migrate to ApplicationContext
        registry.addCloseListener(event -> {
            // Get bootstrap instance
            ExpensiveService service =
                event.getBootstrapContext().get(ExpensiveService.class);

            // Register as Spring bean
            GenericApplicationContext appContext =
                (GenericApplicationContext) event.getApplicationContext();
            appContext.registerBean("expensiveService",
                                   ExpensiveService.class,
                                   () -> service);
        });
    }
}

Complex Bootstrap Scenario

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.bootstrap.BootstrapRegistry;

@SpringBootApplication
public class ComplexBootstrapExample {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(ComplexBootstrapExample.class);

        app.addBootstrapRegistryInitializer(registry -> {
            // Register configuration loader
            registry.register(ConfigurationLoader.class, context -> {
                // Can access other bootstrap instances
                Logger logger = context.getOrElse(Logger.class,
                    new DefaultLogger());
                return new ConfigurationLoader(logger);
            });

            // Register logger
            registry.registerIfAbsent(Logger.class,
                BootstrapRegistry.InstanceSupplier.of(new DefaultLogger()));

            // Add close listener for cleanup
            registry.addCloseListener(event -> {
                Logger logger = event.getBootstrapContext()
                                    .get(Logger.class);
                logger.info("Bootstrap context closed");

                // Optionally migrate to ApplicationContext
                ConfigurationLoader loader =
                    event.getBootstrapContext().get(ConfigurationLoader.class);
                event.getApplicationContext()
                     .getBeanFactory()
                     .registerSingleton("configLoader", loader);
            });
        });

        app.run(args);
    }
}

Common Patterns

Pattern 1: Environment Customization with Property Sources

Add custom property sources early in the lifecycle to provide configuration from external systems.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import java.util.HashMap;
import java.util.Map;

@SpringBootApplication
public class CustomPropertySourceApplication {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(CustomPropertySourceApplication.class);

        // Register listener for environment preparation
        app.addListeners((ApplicationListener<ApplicationEnvironmentPreparedEvent>) event -> {
            ConfigurableEnvironment environment = event.getEnvironment();

            // Add custom property source with high priority
            Map<String, Object> customProperties = new HashMap<>();
            customProperties.put("app.feature.enabled", "true");
            customProperties.put("app.external.url", loadExternalUrl());

            MapPropertySource propertySource =
                new MapPropertySource("customProperties", customProperties);
            environment.getPropertySources().addFirst(propertySource);

            System.out.println("Custom property source added to environment");
        });

        app.run(args);
    }

    private static String loadExternalUrl() {
        // Load from external configuration service
        return "https://api.example.com";
    }
}

Use Cases:

  • Loading configuration from external services (Consul, etcd)
  • Adding computed properties based on environment
  • Overriding default configurations programmatically

Pattern 2: Conditional Bean Registration Based on Events

Dynamically register beans based on application state during lifecycle events.

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.stereotype.Component;

@Component
public class ConditionalBeanRegistrar
        implements ApplicationListener<ApplicationPreparedEvent> {

    @Override
    public void onApplicationEvent(ApplicationPreparedEvent event) {
        GenericApplicationContext context =
            (GenericApplicationContext) event.getApplicationContext();

        // Check environment to decide which bean to register
        String environment = context.getEnvironment()
                                   .getProperty("spring.profiles.active", "default");

        if ("production".equals(environment)) {
            // Register production-specific bean
            context.registerBean("dataSource", ProductionDataSource.class,
                () -> new ProductionDataSource());
        } else {
            // Register development bean
            context.registerBean("dataSource", DevelopmentDataSource.class,
                () -> new DevelopmentDataSource());
        }

        System.out.println("Registered conditional bean for environment: " + environment);
    }
}

class ProductionDataSource {
    public ProductionDataSource() {
        System.out.println("Production DataSource initialized");
    }
}

class DevelopmentDataSource {
    public DevelopmentDataSource() {
        System.out.println("Development DataSource initialized");
    }
}

Use Cases:

  • Profile-based bean registration
  • Dynamic service discovery
  • Feature flag based component initialization

Pattern 3: Application Readiness Health Check Integration

Integrate with health check systems by listening to readiness events.

import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

@Component
public class HealthCheckIntegration {

    private final Path healthCheckFile = Paths.get("/tmp/app-ready");

    @EventListener
    public void onApplicationReady(ApplicationReadyEvent event) {
        try {
            // Create health check file for external monitoring
            Files.createFile(healthCheckFile);

            // Log readiness with timing
            if (event.getTimeTaken() != null) {
                System.out.println("Application ready in " +
                    event.getTimeTaken().toMillis() + "ms");
            }

            // Notify external monitoring system
            notifyHealthCheckSystem(true);

            // Update application availability state
            System.out.println("Health check indicator created: " + healthCheckFile);

        } catch (IOException e) {
            System.err.println("Failed to create health check file: " + e.getMessage());
        }
    }

    @EventListener
    public void onApplicationFailed(ApplicationFailedEvent event) {
        try {
            // Remove health check file on failure
            Files.deleteIfExists(healthCheckFile);

            // Notify monitoring system of failure
            notifyHealthCheckSystem(false);

            System.err.println("Application failed to start: " +
                event.getException().getMessage());

        } catch (IOException e) {
            System.err.println("Failed to cleanup health check file: " + e.getMessage());
        }
    }

    private void notifyHealthCheckSystem(boolean healthy) {
        // Notify external health check system (e.g., Kubernetes readiness probe)
        System.out.println("Health status: " + (healthy ? "READY" : "FAILED"));
    }
}

Use Cases:

  • Kubernetes readiness/liveness probes
  • Load balancer health checks
  • Service mesh integration

Pattern 4: Bootstrap Registry for Expensive Early Initialization

Use the bootstrap registry to share expensive objects across early initialization stages.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.bootstrap.BootstrapRegistry;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationListener;
import java.util.concurrent.atomic.AtomicBoolean;

@SpringBootApplication
public class BootstrapRegistryExample {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(BootstrapRegistryExample.class);

        // Register expensive service in bootstrap registry
        app.addBootstrapRegistryInitializer(registry -> {
            registry.register(ExpensiveCryptoService.class, context -> {
                System.out.println("Initializing expensive crypto service...");
                return new ExpensiveCryptoService();
            });

            // Add close listener to migrate to ApplicationContext
            registry.addCloseListener(event -> {
                ExpensiveCryptoService service =
                    event.getBootstrapContext().get(ExpensiveCryptoService.class);

                // Register as Spring bean
                event.getApplicationContext()
                     .getBeanFactory()
                     .registerSingleton("cryptoService", service);

                System.out.println("Crypto service migrated to ApplicationContext");
            });
        });

        // Use the bootstrap service during environment preparation
        app.addListeners((ApplicationListener<ApplicationEnvironmentPreparedEvent>) event -> {
            ExpensiveCryptoService cryptoService =
                event.getBootstrapContext().get(ExpensiveCryptoService.class);

            // Use service to decrypt configuration
            String encryptedPassword = event.getEnvironment()
                                           .getProperty("database.password.encrypted");
            if (encryptedPassword != null) {
                String decrypted = cryptoService.decrypt(encryptedPassword);
                System.setProperty("database.password", decrypted);
            }
        });

        app.run(args);
    }
}

class ExpensiveCryptoService {
    private final AtomicBoolean initialized = new AtomicBoolean(false);

    public ExpensiveCryptoService() {
        // Simulate expensive initialization
        try {
            Thread.sleep(1000);
            initialized.set(true);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    public String decrypt(String encrypted) {
        if (!initialized.get()) {
            throw new IllegalStateException("Service not initialized");
        }
        // Simplified decryption
        return "decrypted_" + encrypted;
    }
}

Use Cases:

  • Cryptographic services for configuration decryption
  • Connection pools needed during startup
  • Expensive computations required by multiple initializers

Pattern 5: Comprehensive Lifecycle Monitoring and Metrics

Track application startup metrics across all lifecycle stages.

import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.boot.bootstrap.ConfigurableBootstrapContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;

public class StartupMetricsListener implements SpringApplicationRunListener {

    private final SpringApplication application;
    private final String[] args;
    private final Map<String, Instant> timestamps = new HashMap<>();

    // Required constructor
    public StartupMetricsListener(SpringApplication application, String[] args) {
        this.application = application;
        this.args = args;
        timestamps.put("init", Instant.now());
    }

    @Override
    public void starting(ConfigurableBootstrapContext bootstrapContext) {
        timestamps.put("starting", Instant.now());
        logDuration("Initialization to starting", "init", "starting");
    }

    @Override
    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
                                   ConfigurableEnvironment environment) {
        timestamps.put("environmentPrepared", Instant.now());
        logDuration("Starting to environment prepared", "starting", "environmentPrepared");
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        timestamps.put("contextPrepared", Instant.now());
        logDuration("Environment to context prepared", "environmentPrepared", "contextPrepared");
    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        timestamps.put("contextLoaded", Instant.now());
        logDuration("Context prepared to loaded", "contextPrepared", "contextLoaded");
    }

    @Override
    public void started(ConfigurableApplicationContext context, Duration timeTaken) {
        timestamps.put("started", Instant.now());
        logDuration("Context loaded to started", "contextLoaded", "started");
        System.out.println("==> Total time to started: " + timeTaken);
    }

    @Override
    public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
        timestamps.put("ready", Instant.now());
        logDuration("Started to ready", "started", "ready");
        System.out.println("==> Total time to ready: " + timeTaken);
        printSummary();
    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        timestamps.put("failed", Instant.now());
        System.err.println("==> Application failed at: " + timestamps.get("failed"));
        System.err.println("==> Exception: " + exception.getMessage());
        printSummary();
    }

    private void logDuration(String phase, String startKey, String endKey) {
        Instant start = timestamps.get(startKey);
        Instant end = timestamps.get(endKey);
        if (start != null && end != null) {
            Duration duration = Duration.between(start, end);
            System.out.println(String.format("==> %s: %dms", phase, duration.toMillis()));
        }
    }

    private void printSummary() {
        System.out.println("\n=== Startup Metrics Summary ===");
        timestamps.forEach((key, value) ->
            System.out.println(key + ": " + value));
    }
}

Register in META-INF/spring.factories:

org.springframework.boot.SpringApplicationRunListener=\
com.example.StartupMetricsListener

Use Cases:

  • Performance monitoring and optimization
  • Startup time analysis
  • Identifying bottlenecks in initialization

Best Practices

When to Use Each Event

  • ApplicationStartingEvent: Very early initialization, before Environment is available
  • ApplicationEnvironmentPreparedEvent: Customize Environment, add property sources
  • ApplicationContextInitializedEvent: Perform operations after context creation but before bean loading
  • ApplicationPreparedEvent: Last chance to modify context before refresh
  • ApplicationStartedEvent: Access fully initialized beans before runners execute
  • ApplicationReadyEvent: Indicate application is fully started and ready
  • ApplicationFailedEvent: Handle startup failures, cleanup, notifications

Bootstrap Registry Guidelines

  1. Use for expensive objects that are needed early and should be shared
  2. Prefer SINGLETON scope unless you specifically need new instances
  3. Migrate to ApplicationContext if the instance should be available as a Spring bean
  4. Clean up resources in close listeners if necessary
  5. Don't store large objects unnecessarily - the bootstrap context is kept in memory

Performance Considerations

  • Lifecycle events are published synchronously
  • Expensive operations in listeners will delay application startup
  • Consider using @Async for non-critical operations in later events
  • Bootstrap instances are created lazily on first access

Error Handling Best Practices

  1. Always handle exceptions in event listeners - Unhandled exceptions will fail application startup
  2. Log errors with context - Include event type and application state in error messages
  3. Clean up resources in ApplicationFailedEvent - Release any resources acquired in earlier events
  4. Don't swallow exceptions - Let critical errors propagate to fail-fast
@Component
public class RobustEventListener {

    private static final Logger log = LoggerFactory.getLogger(RobustEventListener.class);

    @EventListener
    public void onEnvironmentPrepared(ApplicationEnvironmentPreparedEvent event) {
        try {
            // Perform operation that might fail
            loadExternalConfiguration(event.getEnvironment());
        } catch (Exception e) {
            log.error("Failed to load external configuration", e);
            // Decide: fail-fast or continue with degraded functionality
            throw new IllegalStateException("Critical configuration missing", e);
        }
    }

    private void loadExternalConfiguration(ConfigurableEnvironment environment) {
        // Implementation
    }
}

Security Considerations

  1. Validate external configuration - Don't trust property sources added during Environment preparation
  2. Secure bootstrap registry contents - Don't store sensitive data in plain text
  3. Limit shutdown access - Restrict JMX access if exposing shutdown capabilities
  4. Audit configuration changes - Log all programmatic configuration modifications

Testing Lifecycle Events

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest
@ExtendWith(SpringExtension.class)
class LifecycleEventTest {

    private static final AtomicBoolean readyEventReceived = new AtomicBoolean(false);

    @TestConfiguration
    static class TestConfig {
        @EventListener
        public void onReady(ApplicationReadyEvent event) {
            readyEventReceived.set(true);
        }
    }

    @Test
    void shouldReceiveApplicationReadyEvent() {
        assertThat(readyEventReceived.get()).isTrue();
    }
}

Common Pitfalls to Avoid

  1. Don't perform database operations in early events - Database connections may not be ready
  2. Don't register listeners as beans for early events - ApplicationContext not available yet
  3. Don't modify ApplicationContext after refresh - Register beans before ApplicationPreparedEvent
  4. Don't store mutable state in listeners - Multiple runs will share the same listener instance
  5. Don't block indefinitely - Use timeouts for external service calls
  6. Don't forget to unregister resources - Clean up in ApplicationFailedEvent
  7. Don't rely on execution order - Multiple listeners of the same event have undefined order
  8. Don't access beans during ApplicationStartingEvent - ApplicationContext not created yet

Application Context Initializers

ContextIdApplicationContextInitializer

Built-in ApplicationContextInitializer that sets the Spring ApplicationContext ID based on the spring.application.name property.

Type Definition

package org.springframework.boot.context;

import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.Ordered;

/**
 * {@link ApplicationContextInitializer} that sets the Spring
 * {@link ApplicationContext#getId() ApplicationContext ID}. The
 * {@code spring.application.name} property is used to create the ID. If the property is
 * not set {@code application} is used.
 *
 * @since 1.0.0
 */
public class ContextIdApplicationContextInitializer
        implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {

    /**
     * Set the order for this initializer.
     *
     * @param order the order value
     */
    public void setOrder(int order);

    /**
     * Get the order value for this initializer.
     *
     * @return the order value (defaults to Ordered.LOWEST_PRECEDENCE - 10)
     */
    @Override
    int getOrder();

    /**
     * Initialize the ApplicationContext by setting its ID.
     * The ID is derived from the {@code spring.application.name} property,
     * or defaults to "application" if not set.
     *
     * For child contexts (when a parent context exists), the ID is appended
     * with a sequential number (e.g., "myapp-1", "myapp-2").
     *
     * @param applicationContext the application context to initialize
     */
    @Override
    void initialize(ConfigurableApplicationContext applicationContext);
}

Key Features

  • Automatic ApplicationContext ID Assignment: Sets the context ID based on spring.application.name
  • Default ID: Uses "application" if spring.application.name is not configured
  • Child Context Support: Appends sequential numbers for child contexts (e.g., "myapp-1", "myapp-2")
  • Execution Order: Runs late in the initialization sequence (LOWEST_PRECEDENCE - 10)

Usage

This initializer is automatically registered by Spring Boot and runs during context initialization. The context ID can be accessed programmatically:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

@Component
class ContextIdChecker {

    private final ApplicationContext context;

    public ContextIdChecker(ApplicationContext context) {
        this.context = context;
    }

    @EventListener(ApplicationReadyEvent.class)
    public void checkContextId() {
        System.out.println("ApplicationContext ID: " + context.getId());
        // Output: "myapp" (if spring.application.name=myapp)
        // Output: "application" (if spring.application.name not set)
    }
}

Configuration

Set the application name in application.properties or application.yml:

# application.properties
spring.application.name=myapp
# application.yml
spring:
  application:
    name: myapp

Use Cases

  • Logging and Monitoring: Identify application contexts in logs and metrics
  • Multi-Context Applications: Distinguish between parent and child contexts
  • Distributed Systems: Unique identification in service registries
  • Testing: Verify context configuration in integration tests

Example: Custom ApplicationContextInitializer with Context ID

import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;

public class CustomContextInitializer
        implements ApplicationContextInitializer<ConfigurableApplicationContext> {

    @Override
    public void initialize(ConfigurableApplicationContext context) {
        // Context ID is already set by ContextIdApplicationContextInitializer
        String contextId = context.getId();
        System.out.println("Initializing context: " + contextId);

        // Perform custom initialization based on context ID
        if (contextId.startsWith("myapp")) {
            // Application-specific initialization
        }
    }
}

See Also