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

bootstrap.mddocs/

Bootstrap Context and Registry

Package: org.springframework.boot.bootstrap Since: 4.0.0

Simple object registry available during startup and environment post-processing, before the ApplicationContext is prepared. Used for expensive-to-create objects or objects that need to be shared early in the application lifecycle.

Overview

The bootstrap package provides a lightweight registry and context system available during the earliest stages of Spring Boot application startup. This allows EnvironmentPostProcessors and other early initialization code to share objects before the main ApplicationContext is created.

Key Use Cases:

  • Share expensive-to-create objects across multiple EnvironmentPostProcessors
  • Register instances that need coordination before ApplicationContext creation
  • Provide early-stage dependency injection for configuration loading
  • Transition bootstrap objects into the main ApplicationContext

Core Concepts

Registry vs Context

  • BootstrapRegistry: Write-only interface for registering instances
  • BootstrapContext: Read-only interface for accessing registered instances
  • ConfigurableBootstrapContext: Combines both interfaces for full control

Lifecycle

  1. Bootstrap Phase: BootstrapRegistry created and initialized via BootstrapRegistryInitializers
  2. Environment Post-Processing: EnvironmentPostProcessors can access BootstrapContext
  3. Context Preparation: ApplicationContext being prepared
  4. Closed Event: BootstrapContextClosedEvent published with access to both contexts
  5. Main Application: Normal ApplicationContext lifecycle begins

Capabilities

Bootstrap Registry

Register instances that will be available during early startup phases.

package org.springframework.boot.bootstrap;

import org.springframework.context.ApplicationListener;

/**
 * A simple object registry available during startup and Environment post-processing.
 * @since 4.0.0
 */
public interface BootstrapRegistry {

    /**
     * Register a specific type. Replaces existing registration if not yet obtained as singleton.
     * @param type the instance type
     * @param instanceSupplier the instance supplier
     */
    <T> void register(Class<T> type, InstanceSupplier<T> instanceSupplier);

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

    /**
     * Check if a type is registered.
     * @param type the instance type
     * @return true if registered
     */
    <T> boolean isRegistered(Class<T> type);

    /**
     * Get the registered InstanceSupplier for a type.
     * @param type the instance type
     * @return the InstanceSupplier or null
     */
    <T> InstanceSupplier<T> getRegisteredInstanceSupplier(Class<T> type);

    /**
     * Add listener called when BootstrapContext closes and ApplicationContext is prepared.
     * @param listener the listener to add
     */
    void addCloseListener(ApplicationListener<BootstrapContextClosedEvent> listener);

    /**
     * Supplier for instance creation.
     * @param <T> the instance type
     */
    @FunctionalInterface
    interface InstanceSupplier<T> {

        /**
         * Create the instance when needed.
         * @param context the BootstrapContext for accessing other instances
         * @return the instance or null
         */
        T get(BootstrapContext context);

        /**
         * Get the scope of the instance.
         * @return the scope (default: SINGLETON)
         */
        default Scope getScope() {
            return Scope.SINGLETON;
        }

        /**
         * Return new InstanceSupplier with updated scope.
         * @param scope the new scope
         * @return new InstanceSupplier with specified scope
         */
        default InstanceSupplier<T> withScope(Scope scope);

        /**
         * Create InstanceSupplier from an instance.
         * @param instance the instance
         * @return new InstanceSupplier
         */
        static <T> InstanceSupplier<T> of(T instance);

        /**
         * Create InstanceSupplier from a Supplier.
         * @param supplier the supplier
         * @return new InstanceSupplier
         */
        static <T> InstanceSupplier<T> from(Supplier<T> supplier);
    }

    /**
     * Instance scope.
     */
    enum Scope {
        /** Singleton - created once and cached */
        SINGLETON,
        /** Prototype - created every time it's requested */
        PROTOTYPE
    }
}

Usage Example:

import org.springframework.boot.bootstrap.BootstrapRegistry;
import org.springframework.boot.bootstrap.BootstrapRegistry.InstanceSupplier;
import org.springframework.boot.bootstrap.BootstrapRegistry.Scope;

public void registerBootstrapInstance(BootstrapRegistry registry) {
    // Register singleton instance (created once, cached)
    registry.register(MyExpensiveService.class,
        InstanceSupplier.from(() -> new MyExpensiveService()));

    // Register prototype instance (created each time)
    registry.register(RequestContext.class,
        InstanceSupplier.from(() -> new RequestContext())
            .withScope(Scope.PROTOTYPE));

    // Register with dependency on other bootstrap instance
    registry.register(ConfigLoader.class, context -> {
        MyExpensiveService service = context.get(MyExpensiveService.class);
        return new ConfigLoader(service);
    });

    // Conditional registration
    registry.registerIfAbsent(DefaultLogger.class,
        InstanceSupplier.of(new DefaultLogger()));
}

Bootstrap Context

Read-only access to registered instances during environment post-processing.

package org.springframework.boot.bootstrap;

import java.util.function.Supplier;

/**
 * Read-only access to bootstrap instances.
 * @since 4.0.0
 */
public interface BootstrapContext {

    /**
     * Get a registered instance. Throws if not registered.
     * @param type the instance type
     * @return the instance
     * @throws IllegalStateException if not registered
     */
    <T> T get(Class<T> type) throws IllegalStateException;

    /**
     * Get instance or return default value.
     * @param type the instance type
     * @param other the default value
     * @return the instance or default
     */
    <T> T getOrElse(Class<T> type, T other);

    /**
     * Get instance or supply default value.
     * @param type the instance type
     * @param other supplier for default value
     * @return the instance or supplied default
     */
    <T> T getOrElseSupply(Class<T> type, Supplier<T> other);

    /**
     * Get instance or throw custom exception.
     * @param type the instance type
     * @param exceptionSupplier supplier for exception
     * @return the instance
     * @throws X if not registered
     */
    <T, X extends Throwable> T getOrElseThrow(Class<T> type,
        Supplier<? extends X> exceptionSupplier) throws X;

    /**
     * Check if type is registered.
     * @param type the instance type
     * @return true if registered
     */
    <T> boolean isRegistered(Class<T> type);
}

Usage Example:

import org.springframework.boot.EnvironmentPostProcessor;
import org.springframework.boot.bootstrap.BootstrapContext;
import org.springframework.core.env.ConfigurableEnvironment;

public class MyEnvironmentPostProcessor implements EnvironmentPostProcessor {

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment,
                                       SpringApplication application) {
        BootstrapContext bootstrap = application.getBootstrapContext();

        // Get required instance
        MyExpensiveService service = bootstrap.get(MyExpensiveService.class);

        // Get optional instance with default
        Logger logger = bootstrap.getOrElse(Logger.class, new ConsoleLogger());

        // Get with lazy default
        ConfigLoader loader = bootstrap.getOrElseSupply(ConfigLoader.class,
            () -> new ConfigLoader());

        // Get or throw custom exception
        DataSource dataSource = bootstrap.getOrElseThrow(DataSource.class,
            () -> new IllegalStateException("DataSource not configured"));

        // Conditional logic
        if (bootstrap.isRegistered(CacheManager.class)) {
            CacheManager cache = bootstrap.get(CacheManager.class);
            // Use cache
        }
    }
}

Configurable Bootstrap Context

Combined registry and context interface for full control.

package org.springframework.boot.bootstrap;

/**
 * Configurable BootstrapContext combining registry and context interfaces.
 * @since 4.0.0
 */
public interface ConfigurableBootstrapContext extends BootstrapRegistry, BootstrapContext {
    // Inherits all methods from both BootstrapRegistry and BootstrapContext
}

Bootstrap Registry Initializer

Callback interface for initializing the BootstrapRegistry before use.

package org.springframework.boot.bootstrap;

/**
 * Callback interface for initializing BootstrapRegistry.
 * @since 4.0.0
 */
@FunctionalInterface
public interface BootstrapRegistryInitializer {

    /**
     * Initialize the BootstrapRegistry.
     * @param registry the registry to initialize
     */
    void initialize(BootstrapRegistry registry);
}

Usage Example:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.bootstrap.BootstrapRegistryInitializer;

public class MyApplication {

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

        // Add bootstrap initializer
        app.addBootstrapRegistryInitializer(registry -> {
            // Register instances needed during environment post-processing
            registry.register(MyService.class,
                context -> new MyService());

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

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

        app.run(args);
    }
}

Bootstrap Context Closed Event

Event published when BootstrapContext closes and ApplicationContext is prepared.

package org.springframework.boot.bootstrap;

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

/**
 * Event published when BootstrapContext is closed.
 * @since 4.0.0
 */
public class BootstrapContextClosedEvent extends ApplicationEvent {

    /**
     * Create new event.
     * @param applicationContext the application context
     * @param bootstrapContext the bootstrap context
     */
    public BootstrapContextClosedEvent(ConfigurableApplicationContext applicationContext,
                                       ConfigurableBootstrapContext bootstrapContext);

    /**
     * Get the BootstrapContext being closed.
     * @return the bootstrap context
     */
    public ConfigurableBootstrapContext getBootstrapContext();

    /**
     * Get the prepared ApplicationContext.
     * @return the application context
     */
    public ConfigurableApplicationContext getApplicationContext();
}

Usage Example:

import org.springframework.boot.bootstrap.BootstrapContextClosedEvent;
import org.springframework.boot.bootstrap.BootstrapRegistry;
import org.springframework.context.ApplicationListener;

// Register close listener during bootstrap initialization
registry.addCloseListener(new ApplicationListener<BootstrapContextClosedEvent>() {
    @Override
    public void onApplicationEvent(BootstrapContextClosedEvent event) {
        // Access bootstrap instances
        MyService service = event.getBootstrapContext()
            .get(MyService.class);

        // Migrate to ApplicationContext
        event.getApplicationContext()
            .getBeanFactory()
            .registerSingleton("myService", service);

        // Perform cleanup
        service.initialize();
    }
});

Default Bootstrap Context

Default implementation of ConfigurableBootstrapContext.

package org.springframework.boot.bootstrap;

/**
 * Default implementation of ConfigurableBootstrapContext.
 * @since 4.0.0
 */
public class DefaultBootstrapContext implements ConfigurableBootstrapContext {

    /**
     * Create new DefaultBootstrapContext.
     */
    public DefaultBootstrapContext();

    // Implements all methods from ConfigurableBootstrapContext
}

Common Patterns

Pattern 1: Shared Expensive Resource

Share expensive resources across multiple EnvironmentPostProcessors:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.EnvironmentPostProcessor;
import org.springframework.boot.bootstrap.BootstrapRegistry;
import org.springframework.core.env.ConfigurableEnvironment;

public class MyApplication {

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

        // Register expensive resource once
        app.addBootstrapRegistryInitializer(registry -> {
            registry.register(DatabaseMetadataLoader.class, context -> {
                System.out.println("Creating expensive DatabaseMetadataLoader...");
                return new DatabaseMetadataLoader();
            });
        });

        app.run(args);
    }
}

// Multiple post-processors can share the same instance
class FirstPostProcessor implements EnvironmentPostProcessor {
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment,
                                       SpringApplication application) {
        DatabaseMetadataLoader loader =
            application.getBootstrapContext().get(DatabaseMetadataLoader.class);
        // Use loader - created only once
    }
}

class SecondPostProcessor implements EnvironmentPostProcessor {
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment,
                                       SpringApplication application) {
        DatabaseMetadataLoader loader =
            application.getBootstrapContext().get(DatabaseMetadataLoader.class);
        // Uses same instance as FirstPostProcessor
    }
}

Pattern 2: Bootstrap-to-ApplicationContext Migration

Migrate bootstrap instances into the ApplicationContext:

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

public class MyApplication {

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

        app.addBootstrapRegistryInitializer(registry -> {
            // Register instance
            registry.register(ConfigurationService.class,
                context -> new ConfigurationService());

            // Migrate to ApplicationContext when ready
            registry.addCloseListener(event -> {
                ConfigurationService service = event.getBootstrapContext()
                    .get(ConfigurationService.class);

                // Register as singleton bean
                event.getApplicationContext()
                    .getBeanFactory()
                    .registerSingleton("configurationService", service);
            });
        });

        app.run(args);
    }
}

Pattern 3: Conditional Bootstrap Registration

Register instances conditionally based on environment:

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

public class MyApplication {

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

        app.addBootstrapRegistryInitializer(registry -> {
            // Always register logger
            registry.register(Logger.class,
                context -> new ConsoleLogger());

            // Conditionally register cache based on environment
            registry.register(CacheManager.class, context -> {
                // Access other bootstrap instances
                Logger logger = context.get(Logger.class);

                String profile = System.getenv("SPRING_PROFILES_ACTIVE");
                if ("production".equals(profile)) {
                    logger.log("Using distributed cache");
                    return new RedisCache();
                } else {
                    logger.log("Using in-memory cache");
                    return new InMemoryCache();
                }
            });
        });

        app.run(args);
    }
}

Thread Safety

  • BootstrapRegistry: Thread-safe for registration operations
  • BootstrapContext: Thread-safe for read operations
  • DefaultBootstrapContext: Thread-safe implementation
  • Singleton instances: Created once per type, thread-safe
  • Prototype instances: New instance per access, caller responsible for thread safety

Best Practices

  1. Use for Expensive Resources: Only register genuinely expensive-to-create objects
  2. Prefer Singletons: Use SINGLETON scope unless you specifically need PROTOTYPE
  3. Migrate to ApplicationContext: Use close listeners to migrate instances into main context
  4. Avoid Business Logic: Bootstrap is for infrastructure, not business logic
  5. Document Dependencies: Clearly document which bootstrap instances depend on others
  6. Clean Up: Release resources in close listeners
  7. Test Carefully: Bootstrap code runs very early, test with actual Spring Boot lifecycle

Common Pitfalls

Pitfall 1: Circular Dependencies

Problem: Bootstrap instances that depend on each other circularly.

Solution:

// WRONG - circular dependency
registry.register(ServiceA.class, context ->
    new ServiceA(context.get(ServiceB.class)));
registry.register(ServiceB.class, context ->
    new ServiceB(context.get(ServiceA.class)));

// CORRECT - break the circle
registry.register(SharedConfig.class,
    context -> new SharedConfig());
registry.register(ServiceA.class, context ->
    new ServiceA(context.get(SharedConfig.class)));
registry.register(ServiceB.class, context ->
    new ServiceB(context.get(SharedConfig.class)));

Pitfall 2: Forgetting to Migrate

Problem: Bootstrap instance not migrated to ApplicationContext, unavailable to beans.

Solution:

registry.register(MyService.class, context -> new MyService());

// Always add close listener to migrate
registry.addCloseListener(event -> {
    MyService service = event.getBootstrapContext().get(MyService.class);
    event.getApplicationContext()
        .getBeanFactory()
        .registerSingleton("myService", service);
});

Pitfall 3: Using Wrong Scope

Problem: Using SINGLETON for stateful instances that should be per-request.

Solution:

// WRONG - stateful instance as singleton
registry.register(RequestContext.class,
    InstanceSupplier.from(() -> new RequestContext()));

// CORRECT - use PROTOTYPE for stateful instances
registry.register(RequestContext.class,
    InstanceSupplier.from(() -> new RequestContext())
        .withScope(Scope.PROTOTYPE));

Import Statements

import org.springframework.boot.bootstrap.BootstrapRegistry;
import org.springframework.boot.bootstrap.BootstrapContext;
import org.springframework.boot.bootstrap.ConfigurableBootstrapContext;
import org.springframework.boot.bootstrap.BootstrapRegistryInitializer;
import org.springframework.boot.bootstrap.BootstrapContextClosedEvent;
import org.springframework.boot.bootstrap.DefaultBootstrapContext;
import org.springframework.boot.bootstrap.BootstrapRegistry.InstanceSupplier;
import org.springframework.boot.bootstrap.BootstrapRegistry.Scope;