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

bootstrapping.mddocs/

Application Bootstrapping

Package: org.springframework.boot Primary Class: SpringApplication

Core functionality for starting and configuring Spring Boot applications. Provides both simple static methods for quick startup and extensive configuration options for production use.

Quick Reference

Basic Usage Pattern

// Simplest possible startup
SpringApplication.run(MyApplication.class, args);

// With configuration
SpringApplication app = new SpringApplication(MyApplication.class);
app.setWebApplicationType(WebApplicationType.SERVLET);
app.setBannerMode(Banner.Mode.OFF);
app.run(args);

Key Classes Overview

ClassPurposeThread Safety
SpringApplicationMain bootstrapping classNot thread-safe
SpringApplicationBuilderFluent API builderNot thread-safe
WebApplicationTypeWeb application type enumImmutable
ApplicationRunnerPost-startup callback (parsed args)N/A
CommandLineRunnerPost-startup callback (raw args)N/A
BootstrapRegistryEarly-stage object registryThread-safe
ExitCodeGeneratorExit code providerN/A

Core API: SpringApplication

Class Definition

package org.springframework.boot;

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.io.ResourceLoader;
import java.util.Collection;
import java.util.Set;

/**
 * Main class for bootstrapping and launching a Spring application from a Java main method.
 */
public class SpringApplication {

    // Constants
    public static final String BANNER_LOCATION_PROPERTY = "spring.banner.location";
    public static final String BANNER_LOCATION_PROPERTY_VALUE = "banner.txt";

    // Constructors
    public SpringApplication(Class<?>... primarySources);
    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources);

    // Static run methods - returns ConfigurableApplicationContext
    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args);
    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args);

    // Static main method - convenience entry point
    public static void main(String[] args) throws Exception;

    // Instance run method
    public ConfigurableApplicationContext run(String... args);

    // Static utility methods
    public static int exit(ApplicationContext context, ExitCodeGenerator... exitCodeGenerators);
    public static SpringApplicationShutdownHandlers getShutdownHandlers();
    public static SpringApplication.Augmented from(ThrowingConsumer<String[]> main);
    public static void withHook(SpringApplicationHook hook, Runnable action);
    public static <T> T withHook(SpringApplicationHook hook, ThrowingSupplier<T> action);

    // Configuration methods (return void - method chaining not supported)
    public void setWebApplicationType(WebApplicationType webApplicationType);
    public void setAllowBeanDefinitionOverriding(boolean allowBeanDefinitionOverriding);
    public void setAllowCircularReferences(boolean allowCircularReferences);
    public void setLazyInitialization(boolean lazyInitialization);
    public void setHeadless(boolean headless);
    public void setRegisterShutdownHook(boolean registerShutdownHook);
    public void setBanner(Banner banner);
    public void setBannerMode(Banner.Mode bannerMode);
    public void setLogStartupInfo(boolean logStartupInfo);
    public void setAddCommandLineProperties(boolean addCommandLineProperties);
    public void setAddConversionService(boolean addConversionService);
    public void setDefaultProperties(Map<String, Object> defaultProperties);
    public void setDefaultProperties(Properties defaultProperties);
    public void setAdditionalProfiles(String... profiles);
    public void setBeanNameGenerator(BeanNameGenerator beanNameGenerator);
    public void setEnvironment(@Nullable ConfigurableEnvironment environment);
    public void setEnvironmentPrefix(String environmentPrefix);
    public void setApplicationContextFactory(@Nullable ApplicationContextFactory applicationContextFactory);
    public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers);
    public void addInitializers(ApplicationContextInitializer<?>... initializers);
    public void setListeners(Collection<? extends ApplicationListener<?>> listeners);
    public void addListeners(ApplicationListener<?>... listeners);
    public void setApplicationStartup(ApplicationStartup applicationStartup);
    public void setKeepAlive(boolean keepAlive);
    public void addBootstrapRegistryInitializer(BootstrapRegistryInitializer bootstrapRegistryInitializer);
    public void addPrimarySources(Collection<Class<?>> additionalPrimarySources);
    public void setSources(Set<String> sources);
    public void setResourceLoader(ResourceLoader resourceLoader);
    public void setMainApplicationClass(@Nullable Class<?> mainApplicationClass);

    // Getters
    public @Nullable WebApplicationType getWebApplicationType();
    public Set<String> getAdditionalProfiles();
    public @Nullable String getEnvironmentPrefix();
    public Set<ApplicationContextInitializer<?>> getInitializers();
    public Set<ApplicationListener<?>> getListeners();
    public ApplicationStartup getApplicationStartup();
    public boolean isKeepAlive();
    public Set<String> getSources();
    public Set<Object> getAllSources();
    public @Nullable ResourceLoader getResourceLoader();
    public ClassLoader getClassLoader();
    public @Nullable Class<?> getMainApplicationClass();
}

Return Type Details

IMPORTANT: Most setter methods return void, NOT SpringApplication. For method chaining, use SpringApplicationBuilder instead.

// This DOES NOT work - setters return void
SpringApplication app = new SpringApplication(MyApp.class)
    .setWebApplicationType(WebApplicationType.SERVLET)  // Compilation error!
    .setBannerMode(Banner.Mode.OFF);

// Correct approaches:
// Option 1: Multiple statements
SpringApplication app = new SpringApplication(MyApp.class);
app.setWebApplicationType(WebApplicationType.SERVLET);
app.setBannerMode(Banner.Mode.OFF);
app.run(args);

// Option 2: Use SpringApplicationBuilder for method chaining
new SpringApplicationBuilder(MyApp.class)
    .web(WebApplicationType.SERVLET)
    .bannerMode(Banner.Mode.OFF)
    .run(args);

// Option 3: Use SpringApplication.main() for command-line launch
// When --spring.main.sources is provided, launches those sources
// Usage: java -cp app.jar org.springframework.boot.SpringApplication \
//        --spring.main.sources=com.example.MyApp arg1 arg2

Using SpringApplication.main() as Entry Point

The static main() method provides a convenient way to launch Spring Boot applications directly from the SpringApplication class without writing a custom main method:

// Can be launched via command line:
// java -cp myapp.jar org.springframework.boot.SpringApplication \
//      --spring.main.sources=com.example.MyApplication \
//      --server.port=8080

// Or programmatically
String[] args = {
    "--spring.main.sources=com.example.MyApplication",
    "--server.port=9000"
};
SpringApplication.main(args);

This is useful for:

  • Quick testing without writing a main method
  • Dynamic application launching where source classes are determined at runtime
  • Integration with launchers that expect a standard main(String[]) signature

Note: The --spring.main.sources argument specifies which configuration classes to load.

Configuration Examples

Basic Configuration

import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.Banner;

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

        // Web application type
        app.setWebApplicationType(WebApplicationType.SERVLET);  // or REACTIVE, or NONE

        // Banner configuration
        app.setBannerMode(Banner.Mode.OFF);  // OFF, CONSOLE, or LOG

        // Startup logging
        app.setLogStartupInfo(true);  // Log startup information

        // Run the application
        ConfigurableApplicationContext context = app.run(args);
    }
}

Multiple Configuration Sources

import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.Configuration;

// Main application class
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(
            MyApplication.class,
            AdditionalConfig.class,
            DatabaseConfig.class
        );
        app.run(args);
    }
}

// Additional configuration classes
@Configuration
class AdditionalConfig {
    // Additional beans
}

@Configuration
class DatabaseConfig {
    // Database-specific beans
}

Custom Environment Configuration

import org.springframework.boot.SpringApplication;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.env.MapPropertySource;
import java.util.HashMap;
import java.util.Map;

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

        // Create custom environment
        StandardEnvironment env = new StandardEnvironment();

        // Add custom property source
        Map<String, Object> props = new HashMap<>();
        props.put("custom.property", "value");
        props.put("server.port", "8081");
        env.getPropertySources().addFirst(
            new MapPropertySource("customProps", props)
        );

        // Set active profiles
        env.setActiveProfiles("production", "aws");

        // Apply environment to application
        app.setEnvironment(env);
        app.run(args);
    }
}

Bean Definition Overriding Control

import org.springframework.boot.SpringApplication;

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

        // Allow bean definition overriding (default: false)
        // Set to true if you need to override auto-configured beans
        app.setAllowBeanDefinitionOverriding(true);

        // Control circular references (default: false)
        // Generally recommended to keep false and refactor circular dependencies
        app.setAllowCircularReferences(false);

        app.run(args);
    }
}

Lazy Initialization

import org.springframework.boot.SpringApplication;

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

        // Enable lazy initialization for faster startup
        // Beans are created on first use instead of at startup
        app.setLazyInitialization(true);

        app.run(args);
    }
}

Note: When using lazy initialization, consider excluding specific beans:

import org.springframework.boot.LazyInitializationExcludeFilter;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LazyConfig {

    @Bean
    public LazyInitializationExcludeFilter eagerBeanFilter() {
        return (String beanName, BeanDefinition beanDef, Class<?> beanType) -> {
            // Eagerly initialize beans that implement specific interface
            return InitializationRequired.class.isAssignableFrom(beanType);
        };
    }
}

Adding Listeners and Initializers

import org.springframework.boot.SpringApplication;
import org.springframework.context.ApplicationListener;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;

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

        // Add application listeners
        app.addListeners((ApplicationListener<ApplicationReadyEvent>) event -> {
            System.out.println("Application is ready!");
        });

        // Add context initializers
        app.addInitializers(
            (ApplicationContextInitializer<ConfigurableApplicationContext>) context -> {
                // Customize context before refresh
                System.out.println("Initializing context: " + context.getId());
            }
        );

        app.run(args);
    }
}

Default Properties

import org.springframework.boot.SpringApplication;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

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

        // Option 1: Using Map
        Map<String, Object> defaultProps = new HashMap<>();
        defaultProps.put("server.port", 8080);
        defaultProps.put("spring.application.name", "my-app");
        defaultProps.put("logging.level.root", "INFO");
        app.setDefaultProperties(defaultProps);

        // Option 2: Using Properties
        Properties props = new Properties();
        props.setProperty("server.port", "8080");
        props.setProperty("spring.application.name", "my-app");
        app.setDefaultProperties(props);

        app.run(args);
    }
}

Additional Profiles

import org.springframework.boot.SpringApplication;

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

        // Set additional profiles beyond those specified in properties/args
        app.setAdditionalProfiles("debug", "metrics");

        app.run(args);
    }
}

Static Utility Methods

Application Exit with Exit Codes

import org.springframework.boot.SpringApplication;
import org.springframework.boot.ExitCodeGenerator;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;

public class MyApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context =
            SpringApplication.run(MyApplication.class, args);

        // Exit with code from ExitCodeGenerators
        int exitCode = SpringApplication.exit(context,
            () -> 0,  // Success
            () -> 1   // Additional generator
        );

        System.exit(exitCode);
    }
}

// ExitCodeGenerator component
@Component
public class ApplicationExitCodeGenerator implements ExitCodeGenerator {

    private int exitCode = 0;

    public void setExitCode(int exitCode) {
        this.exitCode = exitCode;
    }

    @Override
    public int getExitCode() {
        return this.exitCode;
    }
}

Shutdown Handlers

import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationShutdownHandlers;

public class MyApplication {
    public static void main(String[] args) {
        // Get shutdown handlers registry
        SpringApplicationShutdownHandlers handlers =
            SpringApplication.getShutdownHandlers();

        // Add custom shutdown action
        Runnable shutdownAction = () -> {
            System.out.println("Performing cleanup...");
            // Cleanup code
        };
        handlers.add(shutdownAction);

        // Run application
        SpringApplication.run(MyApplication.class, args);

        // Later, can remove shutdown action if needed
        handlers.remove(shutdownAction);
    }
}

Note: Shutdown handlers run sequentially AFTER application contexts have closed.

SpringApplicationShutdownHandlers

Interface for adding or removing shutdown handlers that execute when the JVM exits. Unlike JVM shutdown hooks (which run concurrently), Spring Boot shutdown handlers run sequentially and are guaranteed to execute only after all ApplicationContext instances have been closed.

Interface Definition

package org.springframework.boot;

/**
 * Interface that can be used to add or remove code that should run when the JVM is
 * shutdown. Shutdown handlers run sequentially rather than concurrently.
 * Handlers are guaranteed to be called only after registered ApplicationContext
 * instances have been closed.
 *
 * @since 2.5.1
 */
public interface SpringApplicationShutdownHandlers {

    /**
     * Add an action to the handlers that will be run when the JVM exits.
     *
     * @param action the action to add
     */
    void add(Runnable action);

    /**
     * Remove a previously added action so that it no longer runs when the JVM exits.
     *
     * @param action the action to remove
     */
    void remove(Runnable action);
}

Access via SpringApplication

Obtain the shutdown handlers registry using the static method:

SpringApplicationShutdownHandlers handlers = SpringApplication.getShutdownHandlers();

Key Characteristics

  • Sequential Execution: Handlers run one after another in the order they were added
  • Post-Context Shutdown: Guaranteed to run AFTER all ApplicationContext instances are closed
  • JVM-Wide: Single shared registry per JVM
  • Thread Safety: Safe to add/remove handlers from multiple threads
  • Execution Guarantee: Handlers execute even if contexts fail to start (if JVM shutdown occurs)

Comparison with JVM Shutdown Hooks

FeatureSpringApplicationShutdownHandlersRuntime.addShutdownHook()
Execution orderSequential (ordered)Concurrent (unordered)
When executedAfter ApplicationContext closedJVM shutdown (arbitrary timing)
Thread safetyBuilt-inManual synchronization required
Context accessContexts already closedContexts may be closing
Ordering controlYes (add order)No

Usage Examples

Basic Cleanup on Shutdown

import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationShutdownHandlers;

public class MyApplication {
    public static void main(String[] args) {
        // Get shutdown handlers
        SpringApplicationShutdownHandlers handlers =
            SpringApplication.getShutdownHandlers();

        // Add cleanup action
        handlers.add(() -> {
            System.out.println("Performing cleanup...");
            // Close connections, flush buffers, etc.
        });

        // Run application
        SpringApplication.run(MyApplication.class, args);
    }
}

Resource Cleanup with Removal

import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationShutdownHandlers;

public class ResourceManager {

    private final Runnable cleanupAction;

    public ResourceManager() {
        this.cleanupAction = () -> {
            System.out.println("Releasing resources...");
            // Cleanup logic
        };

        // Register cleanup on startup
        SpringApplicationShutdownHandlers handlers =
            SpringApplication.getShutdownHandlers();
        handlers.add(cleanupAction);
    }

    public void close() {
        // Remove handler if resource closed early
        SpringApplicationShutdownHandlers handlers =
            SpringApplication.getShutdownHandlers();
        handlers.remove(cleanupAction);
    }
}

Multiple Shutdown Handlers

import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationShutdownHandlers;

public class MyApplication {
    public static void main(String[] args) {
        SpringApplicationShutdownHandlers handlers =
            SpringApplication.getShutdownHandlers();

        // Handlers execute in registration order
        handlers.add(() -> {
            System.out.println("1. Flushing logs...");
        });

        handlers.add(() -> {
            System.out.println("2. Closing database connections...");
        });

        handlers.add(() -> {
            System.out.println("3. Sending shutdown notification...");
        });

        SpringApplication.run(MyApplication.class, args);

        // On JVM shutdown, output will be:
        // 1. Flushing logs...
        // 2. Closing database connections...
        // 3. Sending shutdown notification...
    }
}

Best Practices

  1. Order Matters: Register critical cleanup first (e.g., flush logs before closing connections)
  2. Keep It Fast: Handlers should complete quickly; JVM has limited shutdown time
  3. No Context Access: Don't try to access ApplicationContext (already closed)
  4. Exception Handling: Catch and log exceptions; don't let one handler prevent others from running
  5. Remove When Done: If resource cleaned up early, remove the handler to avoid unnecessary work

Common Use Cases

  • Flushing buffered logs or metrics
  • Closing external connections (databases, message queues)
  • Releasing file locks or temp resources
  • Sending shutdown notifications
  • Updating status in service registry
  • Writing final checkpoint data

SpringApplicationBuilder

Fluent API for building and customizing SpringApplication with support for hierarchical contexts.

Class Definition

package org.springframework.boot.builder;

import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.ResourceLoader;

/**
 * Builder for SpringApplication with convenient fluent API and context hierarchy support.
 */
public class SpringApplicationBuilder {

    // Constructors
    public SpringApplicationBuilder(Class<?>... sources);
    public SpringApplicationBuilder(ResourceLoader resourceLoader, Class<?>... sources);

    // Build and run (terminal operations)
    public SpringApplication build();
    public SpringApplication build(String... args);
    public ConfigurableApplicationContext run(String... args);

    // Access current state
    public SpringApplication application();
    public ConfigurableApplicationContext context();

    // Hierarchy methods
    public SpringApplicationBuilder child(Class<?>... sources);
    public SpringApplicationBuilder parent(Class<?>... sources);
    public SpringApplicationBuilder parent(ConfigurableApplicationContext parent);
    public SpringApplicationBuilder sibling(Class<?>... sources);
    public SpringApplicationBuilder sibling(Class<?>[] sources, String... args);

    // Fluent configuration methods (all return SpringApplicationBuilder for chaining)
    public SpringApplicationBuilder contextFactory(ApplicationContextFactory factory);
    public SpringApplicationBuilder sources(Class<?>... sources);
    public SpringApplicationBuilder web(WebApplicationType webApplicationType);
    public SpringApplicationBuilder logStartupInfo(boolean logStartupInfo);
    public SpringApplicationBuilder banner(Banner banner);
    public SpringApplicationBuilder bannerMode(Banner.Mode bannerMode);
    public SpringApplicationBuilder headless(boolean headless);
    public SpringApplicationBuilder registerShutdownHook(boolean registerShutdownHook);
    public SpringApplicationBuilder main(Class<?> mainApplicationClass);
    public SpringApplicationBuilder addCommandLineProperties(boolean addCommandLineProperties);
    public SpringApplicationBuilder setAddConversionService(boolean addConversionService);
    public SpringApplicationBuilder addBootstrapRegistryInitializer(
        BootstrapRegistryInitializer bootstrapRegistryInitializer);
    public SpringApplicationBuilder lazyInitialization(boolean lazyInitialization);
    public SpringApplicationBuilder properties(String... defaultProperties);
    public SpringApplicationBuilder properties(Properties defaultProperties);
    public SpringApplicationBuilder properties(Map<String, Object> defaults);
    public SpringApplicationBuilder profiles(String... profiles);
    public SpringApplicationBuilder beanNameGenerator(BeanNameGenerator beanNameGenerator);
    public SpringApplicationBuilder environment(ConfigurableEnvironment environment);
    public SpringApplicationBuilder environmentPrefix(String environmentPrefix);
    public SpringApplicationBuilder resourceLoader(ResourceLoader resourceLoader);
    public SpringApplicationBuilder initializers(ApplicationContextInitializer<?>... initializers);
    public SpringApplicationBuilder listeners(ApplicationListener<?>... listeners);
    public SpringApplicationBuilder applicationStartup(ApplicationStartup applicationStartup);
    public SpringApplicationBuilder allowCircularReferences(boolean allowCircularReferences);
}

Basic Usage

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.Banner;

public class MyApplication {
    public static void main(String[] args) {
        // Simple fluent configuration
        new SpringApplicationBuilder(MyApplication.class)
            .web(WebApplicationType.SERVLET)
            .bannerMode(Banner.Mode.OFF)
            .profiles("production")
            .properties("server.port=8080")
            .run(args);
    }
}

Hierarchical Contexts (Parent-Child)

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.WebApplicationType;
import org.springframework.context.annotation.Configuration;

public class HierarchicalApplication {
    public static void main(String[] args) {
        // Create parent-child context hierarchy
        new SpringApplicationBuilder()
            .sources(ParentConfig.class)
            .web(WebApplicationType.NONE)  // Parent is non-web
            .child(ChildConfig.class)      // Child context
            .web(WebApplicationType.SERVLET)  // Child is web
            .run(args);
    }
}

@Configuration
class ParentConfig {
    // Shared beans available to child
    @Bean
    public DataSource dataSource() {
        return new HikariDataSource();
    }
}

@Configuration
class ChildConfig {
    // Web-specific beans
    // Can inject beans from parent context
    @Bean
    public WebController webController(DataSource dataSource) {
        return new WebController(dataSource);
    }
}

Complex Configuration Example

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.Banner;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;

public class MyApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = new SpringApplicationBuilder()
            // Source classes
            .sources(MyApplication.class, DatabaseConfig.class)

            // Web configuration
            .web(WebApplicationType.SERVLET)

            // Properties
            .properties(
                "server.port=8080",
                "spring.application.name=my-app",
                "logging.level.root=INFO"
            )

            // Profiles
            .profiles("dev", "local")

            // Logging and banner
            .logStartupInfo(true)
            .bannerMode(Banner.Mode.CONSOLE)

            // Lifecycle hooks
            .registerShutdownHook(true)

            // Listeners
            .listeners((ApplicationListener<ApplicationReadyEvent>) event -> {
                System.out.println("Application ready!");
            })

            // Run
            .run(args);

        // Use context as needed
        MyService service = context.getBean(MyService.class);
        service.doWork();
    }
}

ApplicationContextFactory

Strategy interface for creating the ConfigurableApplicationContext used by a SpringApplication. Contexts created by the factory are returned in their default form, with SpringApplication responsible for configuring and refreshing them.

Interface Definition

package org.springframework.boot;

import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;

/**
 * Strategy interface for creating ConfigurableApplicationContext instances.
 * Created contexts should be returned in their default form, with SpringApplication
 * responsible for configuring and refreshing the context.
 *
 * @since 2.4.0
 */
@FunctionalInterface
public interface ApplicationContextFactory {

    /**
     * Default factory implementation that creates an appropriate context
     * for the WebApplicationType.
     */
    ApplicationContextFactory DEFAULT = new DefaultApplicationContextFactory();

    /**
     * Return the Environment type expected to be set on the created context.
     * Used to convert existing environment instances to the correct type.
     *
     * @param webApplicationType the web application type or null
     * @return the expected environment type or null to use default
     * @since 2.6.14
     */
    default @Nullable Class<? extends ConfigurableEnvironment> getEnvironmentType(
            @Nullable WebApplicationType webApplicationType);

    /**
     * Create a new Environment to be set on the created application context.
     * Result must match the type returned by getEnvironmentType().
     *
     * @param webApplicationType the web application type or null
     * @return an environment instance or null to use default
     * @since 2.6.14
     */
    default @Nullable ConfigurableEnvironment createEnvironment(
            @Nullable WebApplicationType webApplicationType);

    /**
     * Creates the ConfigurableApplicationContext for a SpringApplication,
     * respecting the given webApplicationType.
     *
     * @param webApplicationType the web application type
     * @return the newly created application context
     */
    @Nullable ConfigurableApplicationContext create(@Nullable WebApplicationType webApplicationType);

    /**
     * Creates an ApplicationContextFactory that instantiates the given contextClass
     * through its primary constructor.
     *
     * @param contextClass the context class to instantiate
     * @return the factory that instantiates the context class
     */
    static ApplicationContextFactory ofContextClass(
            Class<? extends ConfigurableApplicationContext> contextClass);

    /**
     * Creates an ApplicationContextFactory that creates contexts by calling
     * the given Supplier.
     *
     * @param supplier the context supplier (e.g., AnnotationConfigApplicationContext::new)
     * @return the factory that calls the supplier
     */
    static ApplicationContextFactory of(Supplier<ConfigurableApplicationContext> supplier);
}

Usage Examples

Creating Custom Context Factory

Use the static factory methods to create custom ApplicationContextFactory instances:

import org.springframework.boot.ApplicationContextFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.GenericApplicationContext;

@SpringBootApplication
public class CustomFactoryApplication {

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

        // Option 1: Use ofContextClass() with a context class
        app.setApplicationContextFactory(
            ApplicationContextFactory.ofContextClass(GenericApplicationContext.class)
        );

        // Option 2: Use of() with a Supplier (method reference)
        app.setApplicationContextFactory(
            ApplicationContextFactory.of(AnnotationConfigApplicationContext::new)
        );

        // Option 3: Use of() with a lambda
        app.setApplicationContextFactory(
            ApplicationContextFactory.of(() -> new GenericApplicationContext())
        );

        app.run(args);
    }
}

Custom Factory Implementation

For advanced scenarios, implement the interface directly:

import org.springframework.boot.ApplicationContextFactory;
import org.springframework.boot.WebApplicationType;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;

public class MyCustomContextFactory implements ApplicationContextFactory {

    @Override
    public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
        // Create different context types based on web application type
        return switch (webApplicationType) {
            case SERVLET -> new AnnotationConfigWebApplicationContext();
            case REACTIVE -> new AnnotationConfigReactiveWebApplicationContext();
            case NONE -> new AnnotationConfigApplicationContext();
            default -> null;
        };
    }

    @Override
    public Class<? extends ConfigurableEnvironment> getEnvironmentType(
            WebApplicationType webApplicationType) {
        // Return appropriate environment type for the context
        return switch (webApplicationType) {
            case SERVLET -> StandardServletEnvironment.class;
            case REACTIVE -> StandardReactiveWebEnvironment.class;
            case NONE -> StandardEnvironment.class;
            default -> null;
        };
    }
}
@SpringBootApplication
public class MyApplication {

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

Using with SpringApplicationBuilder

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.support.GenericApplicationContext;

new SpringApplicationBuilder(MyApplication.class)
    .contextFactory(ApplicationContextFactory.ofContextClass(GenericApplicationContext.class))
    .run(args);

Default Factory Behavior

The DEFAULT factory creates contexts based on WebApplicationType:

  • SERVLET: Creates AnnotationConfigServletWebServerApplicationContext
  • REACTIVE: Creates AnnotationConfigReactiveWebServerApplicationContext
  • NONE: Creates AnnotationConfigApplicationContext

When to Use Custom Factories

Custom ApplicationContextFactory implementations are useful for:

  • Testing: Use simpler context types for faster test startup
  • Custom context hierarchies: Create contexts with specific parent-child relationships
  • Special context requirements: Use contexts with custom features or configurations
  • Non-standard deployments: Create contexts for specialized hosting environments

Thread Safety

ApplicationContextFactory implementations should be stateless and thread-safe, as they may be invoked from multiple threads.

SpringApplicationHook

Low-level hook interface for attaching custom SpringApplicationRunListener instances to observe or modify application behavior. Hooks are managed on a per-thread basis, providing isolation when multiple applications run in parallel.

Interface Definition

package org.springframework.boot;

import org.jspecify.annotations.Nullable;

/**
 * Low-level hook for attaching a SpringApplicationRunListener.
 * Managed per-thread for parallel application execution isolation.
 *
 * @since 3.0.0
 */
@FunctionalInterface
public interface SpringApplicationHook {

    /**
     * Return the SpringApplicationRunListener to hook into the given SpringApplication.
     * @param springApplication the source SpringApplication instance
     * @return the SpringApplicationRunListener to attach or null
     */
    @Nullable SpringApplicationRunListener getRunListener(SpringApplication springApplication);
}

Usage Examples

import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationHook;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;

// Example 1: Custom lifecycle listener via hook
public class CustomLifecycleHook implements SpringApplicationHook {
    @Override
    public SpringApplicationRunListener getRunListener(SpringApplication app) {
        return new SpringApplicationRunListener() {
            @Override
            public void starting(ConfigurableBootstrapContext bootstrapContext) {
                System.out.println("Application starting...");
            }

            @Override
            public void contextLoaded(ConfigurableApplicationContext context) {
                System.out.println("Context loaded");
            }

            @Override
            public void started(ConfigurableApplicationContext context, Duration timeTaken) {
                System.out.println("Application started in " + timeTaken.toMillis() + "ms");
            }
        };
    }
}

// Example 2: Using withHook for scoped execution
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplicationHook hook = (springApp) -> new CustomRunListener();

        // Hook applied only for this execution (thread-local)
        ConfigurableApplicationContext context = SpringApplication.withHook(
            hook,
            () -> SpringApplication.run(MyApplication.class, args)
        );
    }
}

// Example 3: Testing with hooks
public class ApplicationTest {
    @Test
    void testApplicationStartup() {
        AtomicBoolean started = new AtomicBoolean(false);

        SpringApplicationHook testHook = (app) -> new SpringApplicationRunListener() {
            @Override
            public void started(ConfigurableApplicationContext ctx, Duration time) {
                started.set(true);
            }
        };

        SpringApplication.withHook(testHook, () -> {
            SpringApplication.run(TestApp.class, new String[0]);
        });

        assertTrue(started.get());
    }
}

Thread Isolation: Hooks are stored in ThreadLocal storage, so multiple applications can run concurrently with different hooks without interference.

SpringApplicationRunListener

Listener interface for the SpringApplication run method lifecycle. SpringApplicationRunListener implementations are loaded via SpringFactoriesLoader and must declare a public constructor that accepts a SpringApplication instance and a String[] of arguments.

Interface Definition

package org.springframework.boot;

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

/**
 * Listener for SpringApplication run method. Loaded through SpringFactoriesLoader.
 * Implementations should declare a public constructor accepting SpringApplication and String[].
 *
 * @since 1.0.0
 */
public interface SpringApplicationRunListener {

    /**
     * Called immediately when the run method has first started.
     * Can be used for very early initialization.
     *
     * @param bootstrapContext the bootstrap context
     */
    default void starting(ConfigurableBootstrapContext bootstrapContext);

    /**
     * Called once the environment has been prepared, but before the
     * ApplicationContext has been created.
     *
     * @param bootstrapContext the bootstrap context
     * @param environment the environment
     */
    default void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
                                    ConfigurableEnvironment environment);

    /**
     * Called once the ApplicationContext has been created and prepared,
     * but before sources have been loaded.
     *
     * @param context the application context
     */
    default void contextPrepared(ConfigurableApplicationContext context);

    /**
     * Called once the application context has been loaded but before
     * it has been refreshed.
     *
     * @param context the application context
     */
    default void contextLoaded(ConfigurableApplicationContext context);

    /**
     * Called after the context has been refreshed and the application has started,
     * but before CommandLineRunners and ApplicationRunners have been called.
     *
     * @param context the application context
     * @param timeTaken the time taken to start the application or 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 CommandLineRunners and ApplicationRunners have been called.
     *
     * @param context the application context
     * @param timeTaken the time taken for the application to be ready or null if unknown
     * @since 2.6.0
     */
    default void ready(ConfigurableApplicationContext context, @Nullable Duration timeTaken);

    /**
     * Called when a failure occurs when running the application.
     *
     * @param context the application context or 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);
}

Lifecycle Timing

The listener methods are called in the following order during normal startup:

  1. starting() - Very first callback, before any processing begins
  2. environmentPrepared() - After Environment created, before ApplicationContext
  3. contextPrepared() - After ApplicationContext created, before sources loaded
  4. contextLoaded() - After sources loaded, before context refresh
  5. started() - After context refresh, before runners execute
  6. ready() - After all runners complete, application fully started
  7. failed() - Called if any stage fails (instead of later callbacks)

Registration via spring.factories

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

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

Usage Example

package com.example;

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;

/**
 * Custom run listener for application lifecycle monitoring.
 */
public class MonitoringRunListener implements SpringApplicationRunListener {

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

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

    @Override
    public void starting(ConfigurableBootstrapContext bootstrapContext) {
        startTime = System.currentTimeMillis();
        System.out.println("Application starting...");
    }

    @Override
    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
                                   ConfigurableEnvironment environment) {
        System.out.println("Environment prepared with profiles: " +
            String.join(",", environment.getActiveProfiles()));
    }

    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        System.out.println("Context prepared: " + context.getId());
    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        System.out.println("Context loaded with " +
            context.getBeanDefinitionCount() + " beans");
    }

    @Override
    public void started(ConfigurableApplicationContext context, Duration timeTaken) {
        System.out.println("Application started in " + timeTaken.toMillis() + "ms");
    }

    @Override
    public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
        System.out.println("Application ready to serve requests");
        long totalTime = System.currentTimeMillis() - startTime;
        System.out.println("Total startup time: " + totalTime + "ms");
    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {
        System.err.println("Application failed to start: " + exception.getMessage());
    }
}

Comparison with SpringApplicationHook

FeatureSpringApplicationRunListenerSpringApplicationHook
Registrationspring.factoriesProgrammatic (ThreadLocal)
Lifecycle coverageAll 7 lifecycle stagesVia custom RunListener
Use caseFramework extensions, monitoringPer-thread customization, testing
ScopeApplication-wideThread-local
ConstructorRequires SpringApplication + argsNot instantiated

When to use:

  • Use SpringApplicationRunListener for application-wide lifecycle monitoring and framework integration
  • Use SpringApplicationHook for thread-local customization (especially in tests)

SpringApplication.Augmented

Fluent API for configuring and running an augmented SpringApplication where additional configuration classes or profiles should be applied to an existing main method.

Class Definition

package org.springframework.boot;

/**
 * Used to configure and run an augmented SpringApplication with additional configuration.
 * Created via SpringApplication.from(main).
 *
 * @since 3.1.0
 */
public static class SpringApplication.Augmented {

    /**
     * Return a new Augmented instance with additional sources.
     * @param sources configuration classes to apply when application runs
     * @return new Augmented instance with merged sources
     */
    public Augmented with(Class<?>... sources);

    /**
     * Return a new Augmented instance with additional profiles.
     * @param profiles the profiles to activate when application runs
     * @return new Augmented instance with merged profiles
     * @since 3.4.0
     */
    public Augmented withAdditionalProfiles(String... profiles);

    /**
     * Run the application using the given args.
     * @param args the main method arguments
     * @return Running instance providing access to the ApplicationContext
     */
    public SpringApplication.Running run(String... args);
}

Usage Examples

import org.springframework.boot.SpringApplication;

// Example 1: Augment existing application with test configuration
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

// Test augmenting main application with additional config
public class IntegrationTest {
    public static void main(String[] args) {
        SpringApplication.Running running = SpringApplication
            .from(MyApplication::main)
            .with(TestConfiguration.class, MockDataConfiguration.class)
            .run(args);

        ConfigurableApplicationContext context = running.getApplicationContext();
        // Use augmented context for testing
        MyService service = context.getBean(MyService.class);
        service.performTestOperations();
    }
}

// Example 2: Augment with additional profiles
public class ProfileAugmentationExample {
    public static void main(String[] args) {
        SpringApplication.Running app = SpringApplication
            .from(MyApplication::main)
            .withAdditionalProfiles("debug", "local-db")
            .run(new String[]{"--spring.application.name=augmented-app"});

        ConfigurableApplicationContext context = app.getApplicationContext();
        String[] profiles = context.getEnvironment().getActiveProfiles();
        System.out.println("Active profiles: " + Arrays.toString(profiles));
    }
}

// Example 3: Chain multiple augmentations
public class ChainedAugmentationExample {
    public static void main(String[] args) {
        SpringApplication.Running running = SpringApplication
            .from(MyApplication::main)
            .with(SecurityConfiguration.class)
            .with(DatabaseConfiguration.class)
            .withAdditionalProfiles("test", "h2")
            .run(args);

        ConfigurableApplicationContext ctx = running.getApplicationContext();
        // Application running with all augmentations applied
    }
}

// Example 4: Augmentation for containerized testing
@SpringBootApplication
public class ContainerizedApp {
    public static void main(String[] args) {
        SpringApplication.run(ContainerizedApp.class, args);
    }
}

public class DockerComposeTest {
    @Test
    void testWithDockerCompose() {
        // Augment application with container-specific configuration
        SpringApplication.Running running = SpringApplication
            .from(ContainerizedApp::main)
            .with(TestcontainersConfiguration.class)
            .withAdditionalProfiles("testcontainers")
            .run(new String[0]);

        ConfigurableApplicationContext context = running.getApplicationContext();

        // Perform tests against augmented application
        DataSource dataSource = context.getBean(DataSource.class);
        assertNotNull(dataSource);

        context.close();
    }
}

Use Cases:

  • Adding test-specific configuration without modifying main application
  • Running application with different profile combinations
  • Integration testing with additional mock or test beans
  • Containerized testing with Testcontainers configurations

SpringApplication.Running

Interface representing a running Spring Boot application, providing access to the application context created by an augmented application run.

Interface Definition

package org.springframework.boot;

import org.springframework.context.ConfigurableApplicationContext;

/**
 * Provides access to details of a SpringApplication run using Augmented.run().
 *
 * @since 3.1.0
 */
public interface SpringApplication.Running {

    /**
     * Return the root ConfigurableApplicationContext of the running application.
     * @return the root application context
     * @throws IllegalStateException if no root context or multiple root contexts found
     */
    ConfigurableApplicationContext getApplicationContext();
}

Usage Examples

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

// Example 1: Access beans from running application
public class ApplicationAccess {
    public static void main(String[] args) {
        SpringApplication.Running running = SpringApplication
            .from(MyApplication::main)
            .with(AdditionalConfig.class)
            .run(args);

        // Get the application context
        ConfigurableApplicationContext context = running.getApplicationContext();

        // Access beans
        MyService service = context.getBean(MyService.class);
        service.execute();

        // Access environment
        String appName = context.getEnvironment().getProperty("spring.application.name");
        System.out.println("Running application: " + appName);

        // Gracefully shutdown when done
        context.close();
    }
}

// Example 2: Testing pattern with Running
public class ApplicationRunner {
    private SpringApplication.Running running;

    public void start(String[] args) {
        this.running = SpringApplication
            .from(MyApplication::main)
            .with(TestConfiguration.class)
            .run(args);
    }

    public <T> T getBean(Class<T> beanClass) {
        return running.getApplicationContext().getBean(beanClass);
    }

    public void stop() {
        if (running != null) {
            running.getApplicationContext().close();
        }
    }
}

// Example 3: Multiple application contexts (parent-child)
public class MultiContextExample {
    public static void main(String[] args) {
        // When using parent-child contexts, Running returns the root context
        SpringApplication.Running running = SpringApplication
            .from(MyApplication::main)
            .run(args);

        ConfigurableApplicationContext rootContext = running.getApplicationContext();

        // Root context has no parent
        assert rootContext.getParent() == null;

        // Child contexts are accessible via root
        String[] beanNames = rootContext.getBeanDefinitionNames();
        System.out.println("Beans in root context: " + beanNames.length);
    }
}

Behavior:

  • Returns the root ApplicationContext when the application has parent-child context hierarchies
  • Throws IllegalStateException if no root context or multiple root contexts exist
  • Context remains running until explicitly closed via context.close()

SpringApplication.AbandonedRunException

Exception thrown when a SpringApplication run is abandoned before the context is fully prepared. This typically occurs when a SpringApplicationHook terminates the application early or when startup is cancelled programmatically.

Class Definition

package org.springframework.boot;

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

/**
 * Exception thrown when a run is abandoned before the context is prepared.
 * Used internally to signal early termination of application startup.
 *
 * Thread Safety: Thread-safe (immutable after construction).
 *
 * @since 3.0.0
 */
public static class AbandonedRunException extends RuntimeException {

    /**
     * Creates a new AbandonedRunException with no associated context.
     */
    public AbandonedRunException() {
        this(null);
    }

    /**
     * Creates a new AbandonedRunException with the specified application context.
     * The context may represent a partially initialized application.
     *
     * @param applicationContext the application context that was being prepared (may be null)
     */
    public AbandonedRunException(@Nullable ConfigurableApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    /**
     * Returns the application context if one was created before the run was abandoned.
     *
     * @return the application context, or null if no context was created
     */
    @Nullable
    public ConfigurableApplicationContext getApplicationContext() {
        return applicationContext;
    }

    private final ConfigurableApplicationContext applicationContext;
}

Usage Examples

Example 1: Hook-Based Early Termination

import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationHook;
import org.springframework.boot.SpringApplication.AbandonedRunException;

public class ConditionalStartupExample {

    public static void main(String[] args) {
        // Hook that abandons startup based on environment check
        SpringApplicationHook validationHook = new SpringApplicationHook() {
            @Override
            public SpringApplicationRunListener create(SpringApplication application) {
                return new SpringApplicationRunListener() {
                    @Override
                    public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
                                                   ConfigurableEnvironment environment) {
                        // Check critical environment variable
                        String requiredValue = environment.getProperty("REQUIRED_CONFIG");
                        if (requiredValue == null) {
                            System.err.println("REQUIRED_CONFIG not set. Abandoning startup.");
                            // Abandon startup - throws AbandonedRunException internally
                            throw new IllegalStateException("Missing required configuration");
                        }
                    }
                };
            }
        };

        try {
            SpringApplication.withHook(validationHook, () -> {
                SpringApplication.run(MyApplication.class, args);
            });
        } catch (AbandonedRunException e) {
            System.err.println("Application startup was abandoned");
            ConfigurableApplicationContext context = e.getApplicationContext();
            if (context != null) {
                System.out.println("Partial context was created: " + context);
                // Cleanup if needed
                context.close();
            }
            System.exit(1);
        }
    }
}

Example 2: Handling Abandoned Runs

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

public class RobustStartupExample {

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

        try {
            ConfigurableApplicationContext context = app.run(args);
            System.out.println("Application started successfully");
            registerShutdownHook(context);

        } catch (AbandonedRunException e) {
            // Startup was abandoned by hook or internal logic
            System.err.println("Application startup abandoned: " + e.getMessage());

            // Check if partial context exists
            ConfigurableApplicationContext partialContext = e.getApplicationContext();
            if (partialContext != null) {
                System.out.println("Cleaning up partial context...");
                try {
                    partialContext.close();
                } catch (Exception closeEx) {
                    System.err.println("Error closing partial context: " + closeEx.getMessage());
                }
            }

            // Exit with error code
            System.exit(10);

        } catch (Exception e) {
            // Other startup failures
            System.err.println("Application failed to start: " + e.getMessage());
            e.printStackTrace();
            System.exit(1);
        }
    }

    private static void registerShutdownHook(ConfigurableApplicationContext context) {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("Shutdown hook triggered");
            context.close();
        }));
    }
}

Example 3: Testing Early Termination

import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationHook;
import org.springframework.boot.SpringApplication.AbandonedRunException;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.*;

public class AbandonedRunTest {

    @Test
    void testAbandonedRun() {
        SpringApplicationHook hook = application -> {
            return new SpringApplicationRunListener() {
                @Override
                public void starting(ConfigurableBootstrapContext bootstrapContext) {
                    // Simulate condition that abandons startup
                    throw new RuntimeException("Simulated abandonment");
                }
            };
        };

        assertThatThrownBy(() -> {
            SpringApplication.withHook(hook, () -> {
                SpringApplication.run(TestApplication.class);
            });
        }).isInstanceOfAny(AbandonedRunException.class, RuntimeException.class);
    }

    @Test
    void testAbandonedRunWithContext() {
        SpringApplication app = new SpringApplication(TestApplication.class);

        try {
            app.run();
            fail("Expected AbandonedRunException");
        } catch (AbandonedRunException e) {
            // Verify exception details
            assertThat(e.getMessage()).contains("abandoned");

            // May have partial context
            ConfigurableApplicationContext context = e.getApplicationContext();
            if (context != null) {
                assertThat(context).isNotNull();
                context.close();
            }
        }
    }
}

When AbandonedRunException is Thrown:

  • SpringApplicationHook throws exception during early lifecycle phases
  • Application startup is programmatically cancelled before context preparation
  • Critical validation fails during environment setup

Handling Strategy:

  1. Catch AbandonedRunException separately from general exceptions
  2. Check for partial context via getApplicationContext()
  3. Clean up any resources in the partial context
  4. Log appropriate error messages
  5. Exit with distinct error code (e.g., 10) to differentiate from normal failures

Thread Safety: Exception is immutable after construction and safe to pass between threads.

WebApplicationType

Enum that determines the type of web application to create.

Enum Definition

package org.springframework.boot;

/**
 * Enum that determines the type of web application.
 */
public enum WebApplicationType {

    /**
     * Application should not run as web application.
     * No embedded web server will be started.
     * Use for: CLI applications, batch jobs, scheduled tasks.
     */
    NONE,

    /**
     * Application should run as servlet-based web application.
     * Starts an embedded servlet web server (Tomcat, Jetty, Undertow).
     * Use for: Traditional Spring MVC web applications, REST APIs.
     * Requires: spring-boot-starter-web or spring-boot-starter-tomcat
     */
    SERVLET,

    /**
     * Application should run as reactive web application.
     * Starts an embedded reactive web server (Netty, Tomcat, Jetty, Undertow).
     * Use for: Reactive Spring WebFlux applications, reactive REST APIs.
     * Requires: spring-boot-starter-webflux
     */
    REACTIVE;

    /**
     * Deduce web application type from classpath.
     * Checks for presence of web-related classes.
     */
    static WebApplicationType deduceFromClasspath();
}

Auto-Detection Logic

Spring Boot automatically detects the web application type based on classpath:

  1. REACTIVE - If WebFlux is present and Spring MVC is NOT present
  2. SERVLET - If Spring MVC is present
  3. NONE - Otherwise

Usage Examples

import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;

// Example 1: Non-web application
public class BatchApplication {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(BatchApplication.class);
        app.setWebApplicationType(WebApplicationType.NONE);
        app.run(args);
    }
}

// Example 2: Reactive application
public class ReactiveApplication {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(ReactiveApplication.class);
        app.setWebApplicationType(WebApplicationType.REACTIVE);
        app.run(args);
    }
}

// Example 3: Traditional servlet application (default if spring-web is present)
public class ServletApplication {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(ServletApplication.class);
        // Explicitly set (though auto-detected if spring-web is present)
        app.setWebApplicationType(WebApplicationType.SERVLET);
        app.run(args);
    }
}

Post-Startup Callbacks

ApplicationRunner

Callback interface that receives parsed application arguments.

package org.springframework.boot;

import org.springframework.boot.ApplicationArguments;

/**
 * Interface for beans that should run when contained within a SpringApplication.
 * Receives ApplicationArguments with parsed command-line options.
 * Extends Runner marker interface for ordering support.
 * Note: Runner is a package-private internal interface, not part of the public API.
 */
@FunctionalInterface
public interface ApplicationRunner extends Runner {
    /**
     * Callback used to run the bean.
     * Called after context has been refreshed and all CommandLineRunner beans called.
     *
     * @param args application arguments with parsed options
     * @throws Exception on error
     */
    void run(ApplicationArguments args) throws Exception;
}

Usage Example

import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.ApplicationArguments;
import org.springframework.stereotype.Component;
import org.springframework.core.annotation.Order;

@Component
@Order(1)  // Lower values have higher priority
public class DataLoader implements ApplicationRunner {

    private final DataRepository repository;

    public DataLoader(DataRepository repository) {
        this.repository = repository;
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        // Check for --import option
        if (args.containsOption("import")) {
            List<String> files = args.getOptionValues("import");
            for (String file : files) {
                System.out.println("Importing data from: " + file);
                repository.importFrom(file);
            }
        }

        // Check for --init-db flag
        if (args.containsOption("init-db")) {
            System.out.println("Initializing database...");
            repository.initialize();
        }

        // Access non-option arguments
        List<String> nonOptionArgs = args.getNonOptionArgs();
        System.out.println("Non-option args: " + nonOptionArgs);

        // Get all source arguments
        String[] sourceArgs = args.getSourceArgs();
        System.out.println("Total arguments: " + sourceArgs.length);
    }
}

// Run with: java -jar app.jar --import=data.csv --init-db file1 file2
// Output:
// Importing data from: data.csv
// Initializing database...
// Non-option args: [file1, file2]
// Total arguments: 4

CommandLineRunner

Callback interface that receives raw string arguments.

package org.springframework.boot;

/**
 * Interface for beans that should run when contained within a SpringApplication.
 * Receives raw String arguments.
 * Extends Runner marker interface for ordering support.
 * Note: Runner is a package-private internal interface, not part of the public API.
 */
@FunctionalInterface
public interface CommandLineRunner extends Runner {
    /**
     * Callback used to run the bean.
     * Called after context has been refreshed.
     *
     * @param args raw command-line arguments
     * @throws Exception on error
     */
    void run(String... args) throws Exception;
}

Usage Example

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import org.springframework.core.annotation.Order;

@Component
@Order(2)  // Runs after Order(1) runners
public class StartupLogger implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println("Application started with " + args.length + " arguments");

        // Log each argument
        for (int i = 0; i < args.length; i++) {
            System.out.println("Arg " + i + ": " + args[i]);
        }

        // Perform startup checks
        performHealthCheck();
        logSystemInfo();
    }

    private void performHealthCheck() {
        System.out.println("Running health check...");
        // Health check logic
    }

    private void logSystemInfo() {
        System.out.println("Java version: " + System.getProperty("java.version"));
        System.out.println("OS: " + System.getProperty("os.name"));
        System.out.println("Available processors: " +
            Runtime.getRuntime().availableProcessors());
    }
}

Execution Order

Both CommandLineRunner and ApplicationRunner beans are executed together in a single pass, sorted by their @Order annotation or Ordered implementation. The type of runner does NOT determine execution order - only the @Order value matters.

Runners are retrieved together via a common Runner marker interface and sorted into a single list before execution. A CommandLineRunner with @Order(2) will execute AFTER an ApplicationRunner with @Order(1).

import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import org.springframework.core.annotation.Order;

@Component
@Order(1)
class FirstCommandLineRunner implements CommandLineRunner {
    public void run(String... args) {
        System.out.println("1. First CommandLineRunner (@Order(1))");
    }
}

@Component
@Order(2)
class FirstApplicationRunner implements ApplicationRunner {
    public void run(ApplicationArguments args) {
        System.out.println("2. First ApplicationRunner (@Order(2))");
    }
}

@Component
@Order(3)
class SecondCommandLineRunner implements CommandLineRunner {
    public void run(String... args) {
        System.out.println("3. Second CommandLineRunner (@Order(3))");
    }
}

@Component
@Order(4)
class SecondApplicationRunner implements ApplicationRunner {
    public void run(ApplicationArguments args) {
        System.out.println("4. Second ApplicationRunner (@Order(4))");
    }
}

// Output shows interleaved execution based on @Order value:
// 1. First CommandLineRunner (@Order(1))
// 2. First ApplicationRunner (@Order(2))
// 3. Second CommandLineRunner (@Order(3))
// 4. Second ApplicationRunner (@Order(4))

ApplicationArguments Interface

Provides access to the arguments that were used to run a SpringApplication, with support for both option arguments (like --foo=bar) and non-option arguments.

package org.springframework.boot;

import java.util.List;
import java.util.Set;

import org.jspecify.annotations.Nullable;

/**
 * Provides access to the arguments that were used to run a SpringApplication.
 *
 * Thread Safety: Implementations are immutable and thread-safe after creation.
 *
 * @since 1.3.0
 */
public interface ApplicationArguments {

    /**
     * Return the raw unprocessed arguments that were passed to the application.
     *
     * @return the arguments (never null)
     */
    String[] getSourceArgs();

    /**
     * Return the names of all option arguments. For example, if the arguments
     * were "--foo=bar --debug" this would return the values ["foo", "debug"].
     *
     * @return the option names or an empty set (never null)
     */
    Set<String> getOptionNames();

    /**
     * Return whether the set of option arguments parsed from the arguments
     * contains an option with the given name.
     *
     * @param name the name to check
     * @return true if the arguments contain an option with the given name
     */
    boolean containsOption(String name);

    /**
     * Return the collection of values associated with the arguments option
     * having the given name.
     *
     * Behavior:
     * - If the option is present and has no argument (e.g. "--foo"), returns
     *   an empty collection ([])
     * - If the option is present and has a single value (e.g. "--foo=bar"),
     *   returns a collection having one element (["bar"])
     * - If the option is present and has multiple values (e.g. "--foo=bar
     *   --foo=baz"), returns a collection having elements for each value
     *   (["bar", "baz"])
     * - If the option is not present, returns null
     *
     * @param name the name of the option
     * @return a list of option values for the given name, or null if not present
     */
    @Nullable List<String> getOptionValues(String name);

    /**
     * Return the collection of non-option arguments parsed.
     * Non-option arguments are those that don't start with "--".
     *
     * @return the non-option arguments or an empty list (never null)
     */
    List<String> getNonOptionArgs();
}

Key Features:

  • Option Arguments: Arguments prefixed with -- (e.g., --foo=bar, --debug)
  • Non-Option Arguments: Positional arguments without -- prefix
  • Multi-Value Support: Same option can be specified multiple times
  • Null Safety: Only getOptionValues() can return null (when option not present)
  • Thread Safety: Immutable and safe for concurrent access

Comprehensive Usage Example

import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.ApplicationArguments;
import org.springframework.stereotype.Component;

@Component
public class ArgumentsProcessor implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        // Get raw arguments
        String[] source = args.getSourceArgs();
        System.out.println("Raw arguments: " + Arrays.toString(source));

        // Check for specific options
        if (args.containsOption("debug")) {
            System.out.println("Debug mode enabled");
            enableDebugMode();
        }

        if (args.containsOption("verbose")) {
            System.out.println("Verbose mode enabled");
            enableVerboseMode();
        }

        // Get option values
        if (args.containsOption("config")) {
            List<String> configs = args.getOptionValues("config");
            System.out.println("Config files: " + configs);
            for (String config : configs) {
                loadConfiguration(config);
            }
        }

        if (args.containsOption("port")) {
            List<String> ports = args.getOptionValues("port");
            if (!ports.isEmpty()) {
                int port = Integer.parseInt(ports.get(0));
                System.out.println("Using port: " + port);
                setPort(port);
            }
        }

        // Get all option names
        Set<String> optionNames = args.getOptionNames();
        System.out.println("All options: " + optionNames);

        // Get non-option arguments
        List<String> nonOptionArgs = args.getNonOptionArgs();
        System.out.println("Non-option arguments: " + nonOptionArgs);

        // Process files specified as non-option args
        for (String file : nonOptionArgs) {
            System.out.println("Processing file: " + file);
            processFile(file);
        }
    }

    private void enableDebugMode() { /* implementation */ }
    private void enableVerboseMode() { /* implementation */ }
    private void loadConfiguration(String config) { /* implementation */ }
    private void setPort(int port) { /* implementation */ }
    private void processFile(String file) { /* implementation */ }
}

// Example command line:
// java -jar app.jar --debug --config=app.properties --config=db.properties --port=8080 file1.txt file2.txt
//
// Output:
// Raw arguments: [--debug, --config=app.properties, --config=db.properties, --port=8080, file1.txt, file2.txt]
// Debug mode enabled
// Config files: [app.properties, db.properties]
// Using port: 8080
// All options: [debug, config, port]
// Non-option arguments: [file1.txt, file2.txt]
// Processing file: file1.txt
// Processing file: file2.txt

Multi-Value Options and Null Handling

This example demonstrates the specific behavior of getOptionValues() including null returns and multi-value handling.

import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.ApplicationArguments;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class AdvancedArgumentsHandler implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        // Example 1: Option not present - returns null
        List<String> missingOption = args.getOptionValues("notpresent");
        if (missingOption == null) {
            System.out.println("Option 'notpresent' was not provided (null)");
        }

        // Example 2: Option present without value - returns empty list
        if (args.containsOption("flag")) {
            List<String> flagValues = args.getOptionValues("flag");
            System.out.println("Flag option values: " + flagValues);
            // Output: Flag option values: []
            System.out.println("Is empty: " + flagValues.isEmpty());
            // Output: Is empty: true
        }

        // Example 3: Option with single value
        if (args.containsOption("name")) {
            List<String> names = args.getOptionValues("name");
            String name = names.get(0);
            System.out.println("Name: " + name);
        }

        // Example 4: Option with multiple values
        if (args.containsOption("include")) {
            List<String> includes = args.getOptionValues("include");
            System.out.println("Include count: " + includes.size());
            for (int i = 0; i < includes.size(); i++) {
                System.out.println("  Include[" + i + "]: " + includes.get(i));
            }
        }

        // Best practice: Check for null before using
        List<String> servers = args.getOptionValues("server");
        if (servers != null) {
            servers.forEach(server -> connectToServer(server));
        } else {
            System.out.println("No servers specified, using defaults");
            connectToDefaultServer();
        }

        // Alternative: Use containsOption first
        if (args.containsOption("timeout")) {
            List<String> timeouts = args.getOptionValues("timeout");
            // Safe - we know it's not null because containsOption returned true
            int timeout = Integer.parseInt(timeouts.get(0));
            setTimeout(timeout);
        }
    }

    private void connectToServer(String server) { /* implementation */ }
    private void connectToDefaultServer() { /* implementation */ }
    private void setTimeout(int timeout) { /* implementation */ }
}

// Example command line 1: Option without value
// java -jar app.jar --flag --name=John
//
// Output:
// Option 'notpresent' was not provided (null)
// Flag option values: []
// Is empty: true
// Name: John

// Example command line 2: Multiple values for same option
// java -jar app.jar --include=*.java --include=*.xml --include=*.properties
//
// Output:
// Include count: 3
//   Include[0]: *.java
//   Include[1]: *.xml
//   Include[2]: *.properties

// Example command line 3: No server option
// java -jar app.jar --name=Test
//
// Output:
// No servers specified, using defaults

Important Notes:

  1. Null Return: getOptionValues() returns null only when the option is not present at all
  2. Empty List: Returns empty list [] when option is present but has no value (e.g., --flag)
  3. Single Value: Returns single-element list when option has one value (e.g., --name=John)
  4. Multiple Values: Returns all values when option appears multiple times (e.g., --include=a --include=b)
  5. Best Practice: Always check for null or use containsOption() first to avoid NullPointerException

DefaultApplicationArguments

The default implementation of ApplicationArguments interface used by Spring Boot.

package org.springframework.boot;

import java.util.List;
import java.util.Set;

import org.jspecify.annotations.Nullable;

/**
 * Default implementation of {@link ApplicationArguments}.
 *
 * Thread Safety: Immutable and thread-safe after creation.
 *
 * @since 1.4.1
 */
public class DefaultApplicationArguments implements ApplicationArguments {

    /**
     * Create a new DefaultApplicationArguments instance.
     *
     * @param args the source arguments (must not be null)
     * @throws IllegalArgumentException if args is null
     */
    public DefaultApplicationArguments(String... args);

    @Override
    public String[] getSourceArgs();

    @Override
    public Set<String> getOptionNames();

    @Override
    public boolean containsOption(String name);

    @Override
    public @Nullable List<String> getOptionValues(String name);

    @Override
    public List<String> getNonOptionArgs();
}

Key Characteristics:

  • Default Implementation: This is the concrete implementation provided when you inject ApplicationArguments
  • Immutable: Once created, the arguments cannot be changed
  • Thread-Safe: Safe to access from multiple threads
  • Null Safety: Constructor rejects null input; only getOptionValues() can return null

Usage Pattern:

import org.springframework.boot.DefaultApplicationArguments;
import org.springframework.boot.ApplicationArguments;

// Typically injected by Spring Boot automatically
@Component
public class MyComponent {

    private final ApplicationArguments args;

    // Spring Boot provides DefaultApplicationArguments instance
    public MyComponent(ApplicationArguments args) {
        this.args = args;  // This is a DefaultApplicationArguments
    }
}

// Manual instantiation (rare - usually let Spring Boot handle this)
String[] commandLineArgs = {"--debug", "--port=8080", "file.txt"};
ApplicationArguments args = new DefaultApplicationArguments(commandLineArgs);

// Use as any ApplicationArguments
if (args.containsOption("debug")) {
    System.out.println("Debug mode enabled");
}

When to Use:

  • Automatic (Recommended): Spring Boot automatically creates and injects this when you request ApplicationArguments
  • Manual Creation: Only needed for testing or when processing arguments outside Spring context
  • Testing: Useful for unit tests to create mock arguments

Implementation Note: Internally uses Spring's SimpleCommandLinePropertySource for parsing command-line arguments according to standard conventions (GNU-style with -- prefix for options).

Banner Customization

Spring Boot displays a startup banner when an application starts. You can customize or disable the banner using the Banner interface and related classes.

Banner

Functional interface for writing custom banners programmatically.

package org.springframework.boot;

import java.io.PrintStream;
import org.jspecify.annotations.Nullable;
import org.springframework.core.env.Environment;

/**
 * Interface for writing a banner programmatically.
 */
@FunctionalInterface
public interface Banner {
    /**
     * Print the banner to the specified print stream.
     *
     * @param environment the Spring environment
     * @param sourceClass the source class for the application or null
     * @param out the output print stream
     */
    void printBanner(Environment environment, @Nullable Class<?> sourceClass, PrintStream out);

    /**
     * Banner display mode enumeration.
     */
    enum Mode {
        /** Disable printing of the banner */
        OFF,
        /** Print the banner to System.out */
        CONSOLE,
        /** Print the banner to the log file */
        LOG
    }
}

ResourceBanner

Banner implementation that prints from a source text resource file.

package org.springframework.boot;

import org.springframework.core.io.Resource;

/**
 * Banner implementation that prints from a Resource.
 * Supports variable substitution from the Environment.
 */
public class ResourceBanner implements Banner {
    /**
     * Create a new ResourceBanner instance.
     *
     * @param resource the resource to load (must exist)
     */
    public ResourceBanner(Resource resource);

    /**
     * Print the banner by reading from the resource.
     *
     * @param environment the Spring environment
     * @param sourceClass the source class for the application or null
     * @param out the output print stream
     */
    @Override
    public void printBanner(Environment environment, @Nullable Class<?> sourceClass, PrintStream out);
}

Usage Examples

Custom Banner Implementation

import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.env.Environment;

import java.io.PrintStream;

@SpringBootApplication
public class MyApplication {

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

        // Set custom banner
        app.setBanner(new Banner() {
            @Override
            public void printBanner(Environment environment, Class<?> sourceClass, PrintStream out) {
                out.println("=================================");
                out.println("   My Custom Application Banner");
                out.println("   Version: " + environment.getProperty("app.version", "1.0.0"));
                out.println("   Profile: " + String.join(",", environment.getActiveProfiles()));
                out.println("=================================");
            }
        });

        app.run(args);
    }
}

Using ResourceBanner

Create a banner text file in src/main/resources/banner.txt:

__  __          _
 |  \/  |_   _   / \   _ __  _ __
 | |\/| | | | | / _ \ | '_ \| '_ \
 | |  | | |_| |/ ___ \| |_) | |_) |
 |_|  |_|\__, /_/   \_\ .__/| .__/
         |___/        |_|   |_|

Application: ${spring.application.name}
Version: ${application.version:unknown}
Spring Boot: ${spring-boot.version}
import org.springframework.boot.ResourceBanner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.io.ClassPathResource;

@SpringBootApplication
public class MyApplication {

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

        // Load banner from resource file
        app.setBanner(new ResourceBanner(new ClassPathResource("banner.txt")));

        app.run(args);
    }
}

Controlling Banner Mode

import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyApplication {

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

        // Disable banner completely
        app.setBannerMode(Banner.Mode.OFF);

        // Or print to log instead of console
        // app.setBannerMode(Banner.Mode.LOG);

        app.run(args);
    }
}

You can also control the banner mode via configuration:

# application.properties
spring.main.banner-mode=off
# Options: off, console, log

Lambda-based Custom Banner

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

@SpringBootApplication
public class MyApplication {

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

        // Lambda banner (Banner is a functional interface)
        app.setBanner((environment, sourceClass, out) -> {
            out.println("╔════════════════════════════════╗");
            out.println("║   Application Starting...     ║");
            out.println("╚════════════════════════════════╝");
        });

        app.run(args);
    }
}

Banner Variable Substitution

When using ResourceBanner with a text file, the following variables are available:

VariableDescriptionExample
${spring.application.name}Application namemy-app
${application.version}Application version1.0.0
${spring-boot.version}Spring Boot version4.0.0
${application.formatted-version}Formatted version(v1.0.0)
${spring-boot.formatted-version}Formatted Boot version(v4.0.0)
${AnsiColor.NAME}ANSI color codes${AnsiColor.BRIGHT_BLUE}
${AnsiBackground.NAME}ANSI background${AnsiBackground.GREEN}
${AnsiStyle.NAME}ANSI styles${AnsiStyle.BOLD}

Example with colors:

${AnsiColor.BRIGHT_CYAN}
  __  __          _
 |  \/  |_   _   / \   _ __  _ __
 | |\/| | | | | / _ \ | '_ \| '_ \
 | |  | | |_| |/ ___ \| |_) | |_) |
 |_|  |_|\__, /_/   \_\ .__/| .__/
         |___/        |_|   |_|
${AnsiColor.DEFAULT}
Application: ${AnsiStyle.BOLD}${spring.application.name}${AnsiStyle.NORMAL}
Version: ${application.version:1.0.0}

Exit Code Management

ExitCodeGenerator

Interface for generating exit codes when application terminates.

package org.springframework.boot;

/**
 * Interface used to generate exit codes when application terminates.
 * Implementations should be registered as beans in the application context.
 */
@FunctionalInterface
public interface ExitCodeGenerator {
    /**
     * Returns the exit code that should be returned.
     * Called when SpringApplication.exit() is invoked.
     *
     * @return the exit code (typically 0 for success, non-zero for error)
     */
    int getExitCode();
}

ExitCodeEvent

Event fired when an application exit code has been determined from an ExitCodeGenerator.

package org.springframework.boot;

import org.springframework.context.ApplicationEvent;

/**
 * Event fired when an application exit code has been determined from an
 * ExitCodeGenerator.
 *
 * @since 1.3.2
 */
public class ExitCodeEvent extends ApplicationEvent {

    /**
     * Create a new ExitCodeEvent instance.
     * @param source the source of the event
     * @param exitCode the exit code
     */
    public ExitCodeEvent(Object source, int exitCode);

    /**
     * Return the exit code that will be used to exit the JVM.
     * @return the exit code
     */
    public int getExitCode();
}

Usage Examples

Basic Exit Code Generator

import org.springframework.boot.ExitCodeGenerator;
import org.springframework.stereotype.Component;

@Component
public class ApplicationExitCodeGenerator implements ExitCodeGenerator {

    private int exitCode = 0;

    public void setExitCode(int exitCode) {
        this.exitCode = exitCode;
    }

    @Override
    public int getExitCode() {
        return this.exitCode;
    }
}

// Usage in main method
public class MyApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context =
            SpringApplication.run(MyApplication.class, args);

        // Get the exit code generator bean
        ApplicationExitCodeGenerator generator =
            context.getBean(ApplicationExitCodeGenerator.class);

        // Exit with generated code
        int exitCode = SpringApplication.exit(context);
        System.exit(exitCode);
    }
}

Exception-Based Exit Codes

import org.springframework.boot.ExitCodeExceptionMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ExitCodeConfig {

    @Bean
    public ExitCodeExceptionMapper exitCodeExceptionMapper() {
        return exception -> {
            if (exception instanceof IllegalArgumentException) {
                return 1;  // Invalid argument
            }
            else if (exception instanceof IllegalStateException) {
                return 2;  // Invalid state
            }
            else if (exception instanceof IOException) {
                return 3;  // I/O error
            }
            else {
                return 99;  // Unknown error
            }
        };
    }
}

Exit Code Event Listener

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

@Component
public class ExitCodeListener {

    @EventListener
    public void onExitCode(ExitCodeEvent event) {
        int exitCode = event.getExitCode();
        System.out.println("Application exiting with code: " + exitCode);

        // Perform cleanup based on exit code
        if (exitCode != 0) {
            System.err.println("Application failed, performing error cleanup");
            performErrorCleanup();
        } else {
            System.out.println("Application succeeded, performing normal cleanup");
            performNormalCleanup();
        }
    }

    private void performErrorCleanup() { /* implementation */ }
    private void performNormalCleanup() { /* implementation */ }
}

SpringBootExceptionReporter

SPI (Service Provider Interface) for custom reporting of SpringApplication startup errors. Implementations are loaded via spring.factories and must provide a constructor accepting a single ConfigurableApplicationContext parameter.

Interface Definition

package org.springframework.boot;

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.support.SpringFactoriesLoader;

/**
 * Callback interface used to support custom reporting of SpringApplication
 * startup errors. Reporters are loaded through SpringFactoriesLoader and must
 * declare a public constructor with a single ConfigurableApplicationContext parameter.
 *
 * @since 2.0.0
 */
@FunctionalInterface
public interface SpringBootExceptionReporter {

    /**
     * Report a startup failure to the user.
     *
     * @param failure the source failure
     * @return true if the failure was reported, false if default reporting should occur
     */
    boolean reportException(Throwable failure);
}

Key Features

  • SPI Mechanism: Loaded automatically via SpringFactoriesLoader
  • Constructor Requirement: Must have public constructor accepting ConfigurableApplicationContext
  • Return Value Control: Return true to suppress default error reporting, false to allow it
  • Multiple Reporters: Multiple implementations can be registered; all will be invoked
  • Functional Interface: Can be implemented as lambda when constructor requirement is met

Implementation Example

package com.example.reporting;

import org.springframework.boot.SpringBootExceptionReporter;
import org.springframework.context.ConfigurableApplicationContext;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.time.Instant;

/**
 * Custom exception reporter that logs failures to a monitoring system.
 */
public class CustomExceptionReporter implements SpringBootExceptionReporter {

    private final ConfigurableApplicationContext context;

    // Required constructor with ConfigurableApplicationContext parameter
    public CustomExceptionReporter(ConfigurableApplicationContext context) {
        this.context = context;
    }

    @Override
    public boolean reportException(Throwable failure) {
        // Log to monitoring system
        logToMonitoring(failure);

        // Log detailed information
        System.err.println("=".repeat(80));
        System.err.println("APPLICATION STARTUP FAILURE");
        System.err.println("=".repeat(80));
        System.err.println("Timestamp: " + Instant.now());
        System.err.println("Application: " + getApplicationName());
        System.err.println("Exception: " + failure.getClass().getName());
        System.err.println("Message: " + failure.getMessage());
        System.err.println("\nStack Trace:");
        System.err.println(getStackTraceAsString(failure));
        System.err.println("=".repeat(80));

        // Return false to allow default Spring Boot error reporting as well
        return false;
    }

    private String getApplicationName() {
        if (context.getEnvironment().containsProperty("spring.application.name")) {
            return context.getEnvironment().getProperty("spring.application.name");
        }
        return "Unknown Application";
    }

    private String getStackTraceAsString(Throwable throwable) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        throwable.printStackTrace(pw);
        return sw.toString();
    }

    private void logToMonitoring(Throwable failure) {
        // Send to external monitoring system (e.g., Sentry, New Relic, Datadog)
        // MonitoringClient.reportError(failure);
    }
}

Registration via spring.factories

Create or update src/main/resources/META-INF/spring.factories:

# SpringBootExceptionReporter implementations
org.springframework.boot.SpringBootExceptionReporter=\
com.example.reporting.CustomExceptionReporter

Advanced Example: Conditional Reporting

package com.example.reporting;

import org.springframework.boot.SpringBootExceptionReporter;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.Environment;

/**
 * Exception reporter that only reports in production environments
 * and provides different handling based on exception type.
 */
public class ProductionExceptionReporter implements SpringBootExceptionReporter {

    private final ConfigurableApplicationContext context;
    private final Environment environment;

    public ProductionExceptionReporter(ConfigurableApplicationContext context) {
        this.context = context;
        this.environment = context.getEnvironment();
    }

    @Override
    public boolean reportException(Throwable failure) {
        // Only report in production
        String[] activeProfiles = environment.getActiveProfiles();
        boolean isProduction = Arrays.asList(activeProfiles).contains("production");

        if (!isProduction) {
            // In non-production, use default reporting
            return false;
        }

        // Handle different exception types
        if (failure instanceof org.springframework.beans.factory.BeanCreationException) {
            reportBeanCreationFailure((BeanCreationException) failure);
        } else if (failure instanceof org.springframework.boot.web.server.WebServerException) {
            reportWebServerFailure((WebServerException) failure);
        } else {
            reportGenericFailure(failure);
        }

        // Send alert to operations team
        sendAlertToOps(failure);

        // Return true to suppress default console output in production
        return true;
    }

    private void reportBeanCreationFailure(BeanCreationException ex) {
        System.err.println("BEAN CREATION FAILURE: " + ex.getBeanName());
        // Log to centralized logging system
    }

    private void reportWebServerFailure(WebServerException ex) {
        System.err.println("WEB SERVER FAILURE: " + ex.getMessage());
        // Log to centralized logging system
    }

    private void reportGenericFailure(Throwable ex) {
        System.err.println("STARTUP FAILURE: " + ex.getMessage());
        // Log to centralized logging system
    }

    private void sendAlertToOps(Throwable failure) {
        // Send PagerDuty/Slack/email alert
        // AlertService.sendCriticalAlert("Application startup failed", failure);
    }
}

Multiple Reporters Example

You can register multiple exception reporters:

# spring.factories
org.springframework.boot.SpringBootExceptionReporter=\
com.example.reporting.MonitoringReporter,\
com.example.reporting.SlackNotificationReporter,\
com.example.reporting.FileLogReporter

All registered reporters will be invoked in the order they are listed. Each reporter's return value is evaluated independently:

  • If any reporter returns true, default reporting may still occur if other reporters return false
  • The final decision depends on the logical OR of all return values

Integration with FailureAnalyzers

SpringBootExceptionReporter works alongside FailureAnalyzer implementations:

  1. FailureAnalyzers run first - Analyze exceptions and provide diagnostic information
  2. SpringBootExceptionReporters run next - Report the failure (with analysis if available)
  3. Default Reporting occurs last - If all reporters return false

This allows you to:

  • Use built-in FailureAnalyzer implementations for diagnostics
  • Add custom SpringBootExceptionReporter for reporting to external systems
  • Keep or suppress default console error output
// Example combining both
public class IntegratedExceptionReporter implements SpringBootExceptionReporter {

    private final ConfigurableApplicationContext context;

    public IntegratedExceptionReporter(ConfigurableApplicationContext context) {
        this.context = context;
    }

    @Override
    public boolean reportException(Throwable failure) {
        // Check if failure analysis is available
        FailureAnalysis analysis = analyzeFailure(failure);

        if (analysis != null) {
            // Report with analysis details
            reportWithAnalysis(failure, analysis);
        } else {
            // Report without analysis
            reportWithoutAnalysis(failure);
        }

        // Allow default reporting to show analysis in console
        return false;
    }

    private FailureAnalysis analyzeFailure(Throwable failure) {
        // FailureAnalyzers have already run; check if analysis exists
        // This is typically done internally by Spring Boot
        return null; // Simplified for example
    }

    private void reportWithAnalysis(Throwable failure, FailureAnalysis analysis) {
        System.err.println("Description: " + analysis.getDescription());
        System.err.println("Action: " + analysis.getAction());
    }

    private void reportWithoutAnalysis(Throwable failure) {
        System.err.println("Failure: " + failure.getMessage());
    }
}

Use Cases

  • Production Monitoring: Report startup failures to monitoring systems (Sentry, New Relic, Datadog)
  • Team Notifications: Send alerts to Slack, PagerDuty, or email when applications fail to start
  • Custom Logging: Write detailed failure information to specialized log files or databases
  • Compliance: Record startup failures for audit trails
  • Diagnostic Enhancement: Add environment-specific context to error reports
  • Silent Failures: Suppress console output in containerized environments, log to stdout/stderr only

LazyInitializationBeanFactoryPostProcessor

BeanFactoryPostProcessor that applies lazy initialization to bean definitions that are not explicitly excluded and have not already had a value set.

Class Definition

package org.springframework.boot;

import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.core.Ordered;

/**
 * BeanFactoryPostProcessor to set lazy-init on bean definitions that are not
 * excluded and have not already had a value explicitly set.
 *
 * Note that SmartInitializingSingletons are automatically excluded from lazy
 * initialization to ensure that their callback method is invoked.
 *
 * Beans that are in the infrastructure role are automatically excluded from
 * lazy initialization, too.
 *
 * @since 2.2.0
 * @see LazyInitializationExcludeFilter
 */
public final class LazyInitializationBeanFactoryPostProcessor
        implements BeanFactoryPostProcessor, Ordered {

    // Inherited from BeanFactoryPostProcessor
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
            throws BeansException;

    // Inherited from Ordered - returns Ordered.HIGHEST_PRECEDENCE
    @Override
    public int getOrder();
}

How It Works

This processor is automatically registered when SpringApplication.setLazyInitialization(true) is called. It processes all bean definitions and sets lazy-init=true on beans that:

  1. Have no explicit lazy-init value - Beans with an explicit lazy-init setting are not modified
  2. Are not excluded by filters - Beans excluded by LazyInitializationExcludeFilter beans remain eager
  3. Are not SmartInitializingSingleton - Automatically excluded to ensure callbacks run
  4. Are not infrastructure beans - Beans with ROLE_INFRASTRUCTURE are automatically excluded

Automatic Exclusions

The processor automatically excludes:

  • SmartInitializingSingleton beans - To ensure afterSingletonsInstantiated() callbacks execute
  • Infrastructure beans - Beans with BeanDefinition.ROLE_INFRASTRUCTURE
  • Beans matching registered filters - Any beans excluded by LazyInitializationExcludeFilter beans

Registration

This processor is automatically registered when lazy initialization is enabled:

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

@SpringBootApplication
public class MyApplication {

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

        // Automatically registers LazyInitializationBeanFactoryPostProcessor
        app.setLazyInitialization(true);

        app.run(args);
    }
}

Manual Registration (Advanced)

In rare cases, you may need to register this processor manually:

import org.springframework.boot.LazyInitializationBeanFactoryPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class LazyConfig {

    @Bean
    public static LazyInitializationBeanFactoryPostProcessor lazyInitProcessor() {
        return new LazyInitializationBeanFactoryPostProcessor();
    }
}

Note: Manual registration is rarely needed. Use SpringApplication.setLazyInitialization(true) instead.

Interaction with LazyInitializationExcludeFilter

This processor consults all registered LazyInitializationExcludeFilter beans to determine which beans should remain eagerly initialized:

import org.springframework.boot.LazyInitializationExcludeFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ExclusionConfig {

    // This filter is automatically consulted by LazyInitializationBeanFactoryPostProcessor
    @Bean
    public static LazyInitializationExcludeFilter excludeHealthChecks() {
        return LazyInitializationExcludeFilter.forBeanTypes(HealthIndicator.class);
    }
}

Order of Execution

The processor runs at Ordered.HIGHEST_PRECEDENCE to ensure it executes before other BeanFactoryPostProcessor implementations that might depend on the final lazy-init state of beans.

LazyInitializationExcludeFilter

Filter interface for excluding specific beans from having lazy initialization applied by LazyInitializationBeanFactoryPostProcessor when SpringApplication.setLazyInitialization(true) is enabled.

Interface Definition

package org.springframework.boot;

import org.springframework.beans.factory.config.BeanDefinition;

/**
 * Filter that can be used to exclude bean definitions from having their lazy-init set
 * by the LazyInitializationBeanFactoryPostProcessor.
 *
 * Primarily intended to allow downstream projects to deal with edge-cases where lazy
 * loading is not appropriate (such as beans that must be initialized at startup).
 *
 * NOTE: Beans of this type are instantiated very early in the application lifecycle,
 * so they should generally be declared static and not have any dependencies.
 *
 * @since 2.2.0
 */
@FunctionalInterface
public interface LazyInitializationExcludeFilter {

    /**
     * Returns true if the specified bean definition should be excluded from
     * having lazy-init automatically set.
     *
     * @param beanName the bean name
     * @param beanDefinition the bean definition
     * @param beanType the bean type
     * @return true if lazy-init should not be automatically set
     */
    boolean isExcluded(String beanName, BeanDefinition beanDefinition, Class<?> beanType);

    /**
     * Factory method that creates a filter for the given bean types.
     * Beans matching any of the specified types will be excluded from lazy initialization.
     *
     * @param types the filtered types (beans assignable to these types will be excluded)
     * @return a new filter instance
     */
    static LazyInitializationExcludeFilter forBeanTypes(Class<?>... types);
}

Usage Examples

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.LazyInitializationExcludeFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.scheduling.annotation.Scheduled;

@SpringBootApplication
public class MyApplication {

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

        // Enable lazy initialization for faster startup
        app.setLazyInitialization(true);

        app.run(args);
    }

    // Example 1: Exclude specific framework types that don't work well with lazy init
    @Bean
    public static LazyInitializationExcludeFilter integrationLazyExcludeFilter() {
        // Spring Integration flows must be eagerly initialized
        return LazyInitializationExcludeFilter.forBeanTypes(IntegrationFlow.class);
    }

    // Example 2: Exclude beans with specific annotation
    @Bean
    public static LazyInitializationExcludeFilter scheduledTaskFilter() {
        return (beanName, beanDefinition, beanType) -> {
            // Eagerly initialize beans with @Scheduled methods
            return beanType.getDeclaredMethods().length > 0 &&
                   Arrays.stream(beanType.getDeclaredMethods())
                         .anyMatch(m -> m.isAnnotationPresent(Scheduled.class));
        };
    }

    // Example 3: Exclude critical beans by name pattern
    @Bean
    public static LazyInitializationExcludeFilter criticalBeansFilter() {
        return (beanName, beanDefinition, beanType) -> {
            // Eagerly initialize health indicators, data sources, and security beans
            return beanName.endsWith("HealthIndicator") ||
                   beanName.endsWith("DataSource") ||
                   beanName.startsWith("security") ||
                   beanType.getSimpleName().contains("Security");
        };
    }

    // Example 4: Exclude beans implementing specific interface
    @Bean
    public static LazyInitializationExcludeFilter startupRequiredFilter() {
        return (beanName, beanDefinition, beanType) -> {
            // Eagerly initialize beans that implement StartupRequired interface
            return StartupRequired.class.isAssignableFrom(beanType);
        };
    }
}

// Marker interface for beans that must be initialized at startup
interface StartupRequired {
    // Marker interface
}

Production Example: Comprehensive Exclusion Strategy

import org.springframework.boot.LazyInitializationExcludeFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.actuate.health.HealthIndicator;
import javax.sql.DataSource;

@Configuration
public class LazyInitializationConfig {

    @Bean
    public static LazyInitializationExcludeFilter productionLazyExcludeFilter() {
        return (String beanName, BeanDefinition beanDef, Class<?> beanType) -> {
            // 1. Exclude all health indicators for immediate health check availability
            if (HealthIndicator.class.isAssignableFrom(beanType)) {
                return true;
            }

            // 2. Exclude data sources and connection pools
            if (DataSource.class.isAssignableFrom(beanType)) {
                return true;
            }

            // 3. Exclude metrics and monitoring beans
            if (beanName.contains("Metric") || beanName.contains("Monitoring")) {
                return true;
            }

            // 4. Exclude security configuration beans
            if (beanType.getPackage() != null &&
                beanType.getPackage().getName().contains(".security")) {
                return true;
            }

            // 5. Exclude beans with custom marker annotation
            if (beanType.isAnnotationPresent(EagerInit.class)) {
                return true;
            }

            return false;
        };
    }
}

// Custom annotation for marking beans that must be eagerly initialized
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface EagerInit {
}

Important Notes

  1. Static Bean Declaration: LazyInitializationExcludeFilter beans should be declared as static to ensure they're instantiated early in the Spring lifecycle before lazy initialization is applied.

  2. No Dependencies: Filter beans should not have dependencies on other beans, as they're instantiated very early in the application startup process.

  3. Multiple Filters: You can define multiple LazyInitializationExcludeFilter beans - all registered filters are consulted, and if any filter returns true for a bean, that bean will be eagerly initialized.

  4. Performance Impact: While lazy initialization improves startup time, excluding too many beans defeats the purpose. Be selective about which beans must be eagerly initialized.

  5. Edge Cases: Certain Spring features don't work well with lazy initialization:

    • Spring Integration DSL flows
    • Scheduled tasks (@Scheduled)
    • Event listeners that must be active at startup
    • Beans that register themselves with external systems during initialization

Common Exclusion Patterns

// Pattern 1: Exclude by type hierarchy
LazyInitializationExcludeFilter.forBeanTypes(
    DataSource.class,
    HealthIndicator.class,
    MetricReader.class
)

// Pattern 2: Exclude by name pattern
(name, def, type) -> name.matches(".*DataSource|.*HealthIndicator|.*Metrics.*")

// Pattern 3: Exclude by package
(name, def, type) -> type.getPackage() != null &&
    type.getPackage().getName().startsWith("com.myapp.critical")

// Pattern 4: Exclude by annotation presence
(name, def, type) -> type.isAnnotationPresent(EagerInit.class) ||
    Arrays.stream(type.getMethods())
          .anyMatch(m -> m.isAnnotationPresent(Scheduled.class))

// Pattern 5: Combine multiple conditions
(name, def, type) ->
    HealthIndicator.class.isAssignableFrom(type) ||
    DataSource.class.isAssignableFrom(type) ||
    name.endsWith("Security") ||
    type.isAnnotationPresent(EagerInit.class)

Bootstrap Registry

Early-stage registry for sharing expensive objects before ApplicationContext is available.

BootstrapRegistry Interface

package org.springframework.boot.bootstrap;

import java.util.function.Supplier;
import org.springframework.context.ApplicationListener;

/**
 * Simple object registry available during startup and Environment post-processing.
 * Registry uses Class as key, so only one instance per type can be stored.
 */
public interface BootstrapRegistry {

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

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

    /**
     * Check if a registration exists for the given type.
     *
     * @param type the instance type
     * @return true if already registered
     */
    <T> boolean isRegistered(Class<T> type);

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

    /**
     * Add a listener called when BootstrapContext is closed.
     *
     * @param listener the listener to add
     */
    void addCloseListener(ApplicationListener<BootstrapContextClosedEvent> listener);

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

        /**
         * Get or create the instance.
         *
         * @param context the bootstrap context for accessing other instances
         * @return the instance (may be null)
         */
        T get(BootstrapContext context);

        /**
         * Get the scope (SINGLETON or PROTOTYPE).
         * Default is SINGLETON.
         */
        default Scope getScope() {
            return Scope.SINGLETON;
        }

        /**
         * Return a new supplier with different scope.
         */
        default InstanceSupplier<T> withScope(Scope scope) {
            InstanceSupplier<T> parent = this;
            return new InstanceSupplier<T>() {
                @Override
                public T get(BootstrapContext context) {
                    return parent.get(context);
                }

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

        /**
         * Create supplier for an existing instance.
         */
        static <T> InstanceSupplier<T> of(T instance) {
            return (context) -> instance;
        }

        /**
         * Create supplier from a Supplier.
         */
        static <T> InstanceSupplier<T> from(Supplier<T> supplier) {
            return (context) -> supplier.get();
        }
    }

    /**
     * Scope for instances.
     */
    enum Scope {
        /** Instance created once and reused */
        SINGLETON,

        /** New instance created on each access */
        PROTOTYPE
    }
}

BootstrapRegistryInitializer

package org.springframework.boot.bootstrap;

/**
 * Callback interface for initializing a BootstrapRegistry before it is used.
 */
@FunctionalInterface
public interface BootstrapRegistryInitializer {
    /**
     * Initialize the given BootstrapRegistry with any required registrations.
     * Called very early in application lifecycle, before Environment is prepared.
     *
     * @param registry the registry to initialize
     */
    void initialize(BootstrapRegistry registry);
}

Complete Usage Example

import org.springframework.boot.SpringApplication;
import org.springframework.boot.bootstrap.BootstrapRegistryInitializer;
import org.springframework.boot.bootstrap.BootstrapRegistry;
import org.springframework.boot.bootstrap.BootstrapContext;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.support.GenericApplicationContext;

// 1. Define an expensive service
class ExpensiveService {
    public ExpensiveService() {
        System.out.println("Creating expensive service (simulating delay)...");
        try { Thread.sleep(2000); } catch (InterruptedException e) {}
    }

    public String getData() {
        return "Important data";
    }
}

// 2. Define a logger service that depends on ExpensiveService
class LoggerService {
    private final ExpensiveService expensiveService;

    public LoggerService(ExpensiveService expensiveService) {
        this.expensiveService = expensiveService;
        System.out.println("Created logger with data: " + expensiveService.getData());
    }
}

// 3. Bootstrap registry initializer
class MyBootstrapInitializer implements BootstrapRegistryInitializer {

    @Override
    public void initialize(BootstrapRegistry registry) {
        // Register expensive service (singleton scope by default)
        registry.register(ExpensiveService.class,
            context -> new ExpensiveService());

        // Register logger service that depends on expensive service
        registry.register(LoggerService.class,
            context -> {
                ExpensiveService expensiveService = context.get(ExpensiveService.class);
                return new LoggerService(expensiveService);
            });

        // Register a prototype-scoped service (new instance each time)
        registry.register(RequestCounter.class,
            BootstrapRegistry.InstanceSupplier
                .from(() -> new RequestCounter())
                .withScope(BootstrapRegistry.Scope.PROTOTYPE));

        // Add close listener to migrate instances to ApplicationContext
        registry.addCloseListener(event -> {
            System.out.println("Bootstrap context closing, migrating to ApplicationContext");

            // Get instance from bootstrap context
            ExpensiveService service = event.getBootstrapContext()
                .get(ExpensiveService.class);

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

// 4. Listener that uses bootstrap context
class EnvironmentInitializer
        implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {

    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        BootstrapContext bootstrapContext = event.getBootstrapContext();

        // Access registered instances
        ExpensiveService service = bootstrapContext.get(ExpensiveService.class);
        System.out.println("Using bootstrap service: " + service.getData());

        // Access prototype-scoped instance (new instance each time)
        RequestCounter counter1 = bootstrapContext.get(RequestCounter.class);
        RequestCounter counter2 = bootstrapContext.get(RequestCounter.class);
        System.out.println("Counters are different: " + (counter1 != counter2));

        // Check if type is registered
        boolean hasLogger = bootstrapContext.isRegistered(LoggerService.class);
        System.out.println("Logger registered: " + hasLogger);
    }
}

// 5. Main application
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(MyApplication.class);

        // Add bootstrap registry initializer
        app.addBootstrapRegistryInitializer(new MyBootstrapInitializer());

        // Add early listener
        app.addListeners(new EnvironmentInitializer());

        ConfigurableApplicationContext context = app.run(args);

        // After migration, can access as regular bean
        ExpensiveService service = context.getBean(ExpensiveService.class);
        System.out.println("Using bean: " + service.getData());
    }
}

class RequestCounter {
    private static int instanceCount = 0;
    private final int id;

    public RequestCounter() {
        this.id = ++instanceCount;
    }

    public int getId() {
        return id;
    }
}

Annotations

@SpringBootConfiguration

Indicates that a class provides Spring Boot application configuration. Alternative to Spring's @Configuration that enables automatic discovery.

package org.springframework.boot;

import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.*;

/**
 * Indicates that a class provides Spring Boot application configuration.
 * Alternative to @Configuration for enabling automatic discovery (e.g., in tests).
 *
 * Application should only include ONE @SpringBootConfiguration.
 * Most applications inherit this from @SpringBootApplication.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {

    /**
     * Specify whether @Bean methods should be proxied for inter-bean references.
     * When true: Creates CGLIB subclass for enforcing bean lifecycle
     * When false: "Lite mode" - no proxying, better startup performance
     *
     * Default: true
     */
    @AliasFor(annotation = Configuration.class)
    boolean proxyBeanMethods() default true;
}

Usage Examples

// Example 1: Standard usage (inherited from @SpringBootApplication)
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

// Example 2: Explicit @SpringBootConfiguration (rare)
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

// Example 3: Lite mode for improved startup performance
@SpringBootConfiguration(proxyBeanMethods = false)
@EnableAutoConfiguration
@ComponentScan
public class MyApplication {

    @Bean
    public MyService myService() {
        return new MyService();
    }

    @Bean
    public MyController myController() {
        // Direct instantiation - no proxy interception
        // Cannot call myService() here to get singleton
        return new MyController(new MyService());  // Creates new instance
    }

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

// Example 4: Full mode (default) with inter-bean calls
@SpringBootConfiguration(proxyBeanMethods = true)  // Default behavior
public class MyApplication {

    @Bean
    public MyService myService() {
        return new MyService();
    }

    @Bean
    public MyController myController() {
        // Calls myService() method, which is proxied
        // Returns the singleton bean, not new instance
        return new MyController(myService());  // Gets singleton
    }
}

// Example 5: Test configuration discovery
@SpringBootConfiguration
class TestConfiguration {
    @Bean
    public MockBean mockBean() {
        return new MockBean();
    }
}

// Test will automatically find TestConfiguration due to @SpringBootConfiguration
@SpringBootTest
class MyTest {
    @Autowired
    private MockBean mockBean;

    @Test
    void testSomething() {
        // mockBean is automatically available
    }
}

Key Points

  1. Only One Per Application: Application should have exactly one @SpringBootConfiguration
  2. Typically Inherited: Most applications get this from @SpringBootApplication
  3. Test Discovery: Enables automatic configuration discovery in @SpringBootTest
  4. Lite Mode Benefits:
    • Faster startup (no CGLIB proxying)
    • Reduced memory footprint
    • Better GraalVM native image compatibility
  5. Lite Mode Limitations:
    • Cannot use inter-bean method calls
    • Must use dependency injection for bean references

Complete Working Examples

Example 1: Simple Application

package com.example.simple;

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

@SpringBootApplication
public class SimpleApplication {

    public static void main(String[] args) {
        // Most basic startup
        SpringApplication.run(SimpleApplication.class, args);
    }
}

Example 2: Customized Application

package com.example.custom;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.Banner;
import org.springframework.boot.WebApplicationType;
import java.util.HashMap;
import java.util.Map;

@SpringBootApplication
public class CustomApplication {

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

        // Web application type
        app.setWebApplicationType(WebApplicationType.SERVLET);

        // Banner configuration
        app.setBannerMode(Banner.Mode.CONSOLE);

        // Default properties
        Map<String, Object> defaults = new HashMap<>();
        defaults.put("server.port", "8080");
        defaults.put("spring.application.name", "custom-app");
        defaults.put("logging.level.root", "INFO");
        defaults.put("logging.level.com.example", "DEBUG");
        app.setDefaultProperties(defaults);

        // Additional profiles
        app.setAdditionalProfiles("metrics", "health");

        // Startup configuration
        app.setLogStartupInfo(true);
        app.setRegisterShutdownHook(true);

        // Run
        app.run(args);
    }
}

Example 3: Application with Runners

package com.example.runners;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.ApplicationArguments;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;

@SpringBootApplication
public class RunnersApplication {

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

    @Bean
    @Order(1)
    public CommandLineRunner initDatabase() {
        return args -> {
            System.out.println("Initializing database...");
            // Database initialization
        };
    }

    @Bean
    @Order(2)
    public ApplicationRunner loadData() {
        return args -> {
            System.out.println("Loading initial data...");
            if (args.containsOption("seed")) {
                System.out.println("Seeding database with test data");
                // Seed data
            }
        };
    }

    @Bean
    @Order(3)
    public ApplicationRunner performHealthCheck() {
        return args -> {
            System.out.println("Performing health check...");
            // Health check logic
        };
    }
}

Example 4: Hierarchical Context Application

package com.example.hierarchy;

import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;

@SpringBootApplication
public class HierarchyApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder()
            .sources(ParentConfiguration.class)
            .web(WebApplicationType.NONE)
            .properties("spring.application.name=parent-context")
            .child(ChildConfiguration.class)
            .web(WebApplicationType.SERVLET)
            .properties(
                "spring.application.name=child-context",
                "server.port=8080"
            )
            .run(args);
    }
}

@Configuration
class ParentConfiguration {

    @Bean
    public DataSource dataSource() {
        // Create and configure DataSource
        return new HikariDataSource();
    }

    @Bean
    public SharedService sharedService() {
        return new SharedService();
    }
}

@Configuration
class ChildConfiguration {

    @Bean
    public WebController webController(DataSource dataSource, SharedService sharedService) {
        // Can inject beans from parent context
        return new WebController(dataSource, sharedService);
    }

    @Bean
    public RestController restController(DataSource dataSource) {
        return new RestController(dataSource);
    }
}

Example 5: Bootstrap Registry Application

package com.example.bootstrap;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.BootstrapRegistryInitializer;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@SpringBootApplication
public class BootstrapApplication {

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

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

            registry.addCloseListener(event -> {
                ConfigurationService service =
                    event.getBootstrapContext().get(ConfigurationService.class);

                // Migrate to application context
                GenericApplicationContext appContext =
                    (GenericApplicationContext) event.getApplicationContext();
                appContext.registerBean("configurationService",
                    ConfigurationService.class,
                    () -> service);
            });
        });

        // Add early listener
        app.addListeners((ApplicationListener<ApplicationEnvironmentPreparedEvent>) event -> {
            ConfigurationService service =
                event.getBootstrapContext().get(ConfigurationService.class);

            // Use service during environment preparation
            service.loadConfiguration(event.getEnvironment());
        });

        app.run(args);
    }
}

class ConfigurationService {
    public void loadConfiguration(ConfigurableEnvironment environment) {
        System.out.println("Loading configuration during bootstrap");
        // Configuration loading logic
    }
}

Thread Safety and Lifecycle

Thread Safety

  • SpringApplication - Not thread-safe, create separate instance per thread
  • SpringApplicationBuilder - Not thread-safe, create separate instance per thread
  • BootstrapRegistry - Thread-safe, can be accessed from multiple threads
  • ApplicationArguments - Thread-safe, immutable after creation
  • Runners - Not guaranteed thread-safe, depends on implementation

Lifecycle Guarantees

  1. BootstrapRegistry initialization - Before Environment is created
  2. ApplicationStartingEvent - After BootstrapRegistry, before Environment
  3. Environment preparation - After ApplicationStartingEvent
  4. ApplicationEnvironmentPreparedEvent - After Environment is ready
  5. ApplicationContext creation - After Environment is prepared
  6. ApplicationContextInitializedEvent - After context initialization
  7. ApplicationPreparedEvent - After bean definitions loaded
  8. ApplicationContext refresh - Bean instantiation
  9. ApplicationStartedEvent - After context refresh, before runners
  10. CommandLineRunner execution - In order by @Order
  11. ApplicationRunner execution - In order by @Order
  12. ApplicationReadyEvent - After all runners complete successfully
  13. Application serving requests - Application is fully started

Error Handling

If any exception occurs during startup:

  • ApplicationFailedEvent is published
  • Exception is propagated to caller
  • Application context is closed if it was created
  • JVM may exit depending on configuration

Best Practices

  1. Use SpringApplicationBuilder for complex setups - Provides cleaner fluent API
  2. Prefer ApplicationRunner over CommandLineRunner - Parsed arguments are more convenient
  3. Use @Order for runner execution control - Explicit ordering prevents surprises
  4. Register early listeners directly with SpringApplication - Component scanning happens too late
  5. Use BootstrapRegistry for expensive early-stage objects - Avoid recreating expensive resources
  6. Migrate bootstrap instances to ApplicationContext - Use close listeners for migration
  7. Set proxyBeanMethods=false for lite mode - Better startup performance in many cases
  8. Explicitly set WebApplicationType when needed - Don't rely on classpath detection for critical apps
  9. Use exit code generators for proper shutdown - Enables monitoring and automation
  10. Register shutdown hooks in production - Ensures graceful shutdown

Common Patterns

Pattern 1: CLI Application

@SpringBootApplication
public class CliApplication implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(CliApplication.class);
        app.setWebApplicationType(WebApplicationType.NONE);
        System.exit(SpringApplication.exit(app.run(args)));
    }

    @Override
    public void run(String... args) throws Exception {
        // CLI logic
        if (args.length == 0) {
            System.err.println("Usage: app <command>");
            throw new IllegalArgumentException("No command provided");
        }

        String command = args[0];
        switch (command) {
            case "import" -> importData();
            case "export" -> exportData();
            default -> throw new IllegalArgumentException("Unknown command: " + command);
        }
    }

    private void importData() { /* implementation */ }
    private void exportData() { /* implementation */ }
}

Pattern 2: Conditional Bean Registration

@SpringBootApplication
public class ConditionalApplication {

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

        // Conditionally enable features based on arguments
        if (Arrays.asList(args).contains("--enable-caching")) {
            app.setAdditionalProfiles("caching");
        }

        if (Arrays.asList(args).contains("--enable-security")) {
            app.setAdditionalProfiles("security");
        }

        app.run(args);
    }
}

Pattern 3: Dynamic Configuration

@SpringBootApplication
public class DynamicApplication {

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

        // Dynamic configuration based on environment
        String env = System.getProperty("APP_ENV", "dev");

        Map<String, Object> props = new HashMap<>();
        if ("production".equals(env)) {
            props.put("logging.level.root", "WARN");
            props.put("server.port", "80");
        } else {
            props.put("logging.level.root", "DEBUG");
            props.put("server.port", "8080");
        }

        app.setDefaultProperties(props);
        app.run(args);
    }
}

Common Pitfalls

1. Modifying SpringApplication After run()

Problem: Attempting to configure SpringApplication after calling run().

// WRONG - configuration after run() has no effect
SpringApplication app = new SpringApplication(MyApp.class);
ConfigurableApplicationContext context = app.run(args);
app.setBannerMode(Banner.Mode.OFF); // TOO LATE - context already created

Solution: Configure before calling run().

// CORRECT
SpringApplication app = new SpringApplication(MyApp.class);
app.setBannerMode(Banner.Mode.OFF);
app.setWebApplicationType(WebApplicationType.SERVLET);
ConfigurableApplicationContext context = app.run(args);

Why It Fails: The run() method creates and initializes the application context. Any configuration changes after this point are ignored because the context is already built.

2. Thread Safety - Reusing SpringApplication Across Threads

Problem: Sharing a single SpringApplication instance across multiple threads.

// WRONG - SpringApplication is not thread-safe
private static final SpringApplication app = new SpringApplication(MyApp.class);

public void startInThread() {
    new Thread(() -> app.run(args)).start(); // UNSAFE
    new Thread(() -> app.run(args)).start(); // UNSAFE
}

Solution: Create a new instance per thread or use proper synchronization.

// CORRECT - new instance per thread
public void startInThread() {
    new Thread(() -> {
        SpringApplication app = new SpringApplication(MyApp.class);
        app.run(args);
    }).start();
}

Why It Fails: SpringApplication maintains mutable state during startup. Concurrent access causes race conditions and unpredictable behavior.

3. Circular References with setAllowCircularReferences(false)

Problem: Enabling strict circular reference checking breaks existing code.

// Configuration
SpringApplication app = new SpringApplication(MyApp.class);
app.setAllowCircularReferences(false); // Strict mode
app.run(args);

// Beans with circular dependency
@Service
class ServiceA {
    @Autowired private ServiceB serviceB; // Circular!
}

@Service
class ServiceB {
    @Autowired private ServiceA serviceA; // Circular!
}

Error:

BeanCurrentlyInCreationException: Circular dependency between 'serviceA' and 'serviceB'

Solution: Refactor to eliminate circular dependencies using one of these patterns:

// Pattern 1: Constructor injection + @Lazy
@Service
class ServiceA {
    private final ServiceB serviceB;

    public ServiceA(@Lazy ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

// Pattern 2: Setter injection
@Service
class ServiceA {
    private ServiceB serviceB;

    @Autowired
    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

// Pattern 3: Refactor to eliminate dependency
@Service
class ServiceA {
    private final EventPublisher eventPublisher;

    public ServiceA(EventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }
}

Best Practice: Design beans with clear dependency hierarchies. Circular dependencies indicate poor design.

4. Forgetting to Call run() - Silent Failure

Problem: Creating SpringApplication but forgetting to call run().

// WRONG - application never starts
public static void main(String[] args) {
    SpringApplication app = new SpringApplication(MyApp.class);
    app.setBannerMode(Banner.Mode.OFF);
    // Missing: app.run(args);
    System.out.println("Application configured"); // Prints, but app doesn't start
}

Solution: Always call run() to start the application.

// CORRECT
public static void main(String[] args) {
    SpringApplication app = new SpringApplication(MyApp.class);
    app.setBannerMode(Banner.Mode.OFF);
    ConfigurableApplicationContext context = app.run(args); // MUST call run()
}

Why It Fails: Without calling run(), the Spring container is never created, no beans are initialized, and the application exits immediately.

5. WebApplicationType Misconfiguration

Problem: Wrong WebApplicationType for the available dependencies.

// WRONG - forcing REACTIVE when only servlet dependencies exist
SpringApplication app = new SpringApplication(MyApp.class);
app.setWebApplicationType(WebApplicationType.REACTIVE);
app.run(args);

Error:

ApplicationContextException: Unable to start ReactiveWebApplicationContext due to missing reactive dependencies

Solution: Let Spring Boot auto-detect or ensure dependencies match:

// Option 1: Auto-detect (recommended)
SpringApplication app = new SpringApplication(MyApp.class);
// Don't set web type - let Boot detect from classpath
app.run(args);

// Option 2: Explicit type matching dependencies
// For servlet apps (spring-boot-starter-web)
app.setWebApplicationType(WebApplicationType.SERVLET);

// For reactive apps (spring-boot-starter-webflux)
app.setWebApplicationType(WebApplicationType.REACTIVE);

// For non-web apps
app.setWebApplicationType(WebApplicationType.NONE);

Dependencies:

  • WebApplicationType.SERVLET requires spring-boot-starter-web
  • WebApplicationType.REACTIVE requires spring-boot-starter-webflux
  • WebApplicationType.NONE requires neither

6. LazyInitialization Breaking Startup Validation

Problem: Enabling lazy initialization hides startup errors.

// Lazy initialization enabled
SpringApplication app = new SpringApplication(MyApp.class);
app.setLazyInitialization(true);
app.run(args); // Starts successfully...

// But failures occur later at runtime
@Service
class BrokenService {
    @Autowired
    private NonExistentBean bean; // Error only when first accessed!
}

Solution: Use lazy initialization carefully and add health checks:

// Option 1: Disable for critical beans
@Configuration
public class Config {
    @Bean
    public LazyInitializationExcludeFilter lazyExcludeFilter() {
        return (name, definition, type) -> {
            // Always eagerly initialize critical beans
            return name.endsWith("HealthIndicator") ||
                   name.endsWith("DataSource") ||
                   Critical.class.isAssignableFrom(type);
        };
    }
}

// Option 2: Validate on startup with health check
@Component
class StartupValidator implements ApplicationRunner {
    @Autowired
    private BrokenService service;

    @Override
    public void run(ApplicationArguments args) {
        service.validateConfiguration(); // Force initialization
    }
}

Best Practice: Only use lazy initialization in development or when you have comprehensive health checks.

7. Multiple @SpringBootApplication Annotations

Problem: Multiple classes annotated with @SpringBootApplication in the classpath.

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

// Class 2 (e.g., in test package)
@SpringBootApplication
public class TestApp {
    // Another main class
}

Error (in tests):

IllegalStateException: Found multiple @SpringBootConfiguration annotated classes

Solution: Use @SpringBootConfiguration only once per application:

// Main application
@SpringBootApplication
public class MainApp { }

// Test configuration - use @TestConfiguration instead
@TestConfiguration
@EnableAutoConfiguration
@ComponentScan
public class TestConfig {
    // Test-specific beans
}

// Or use @SpringBootTest with specific classes
@SpringBootTest(classes = MainApp.class)
class MyTest { }

Rule: Only one @SpringBootApplication or @SpringBootConfiguration per application context.

8. Incorrect CommandLineRunner Ordering

Problem: Runners execute in wrong order, causing dependencies to fail.

@Component
class DataLoader implements CommandLineRunner {
    @Override
    public void run(String... args) {
        loadData(); // Needs database to be ready
    }
}

@Component
class DatabaseInitializer implements CommandLineRunner {
    @Override
    public void run(String... args) {
        initializeDatabase(); // Must run first
    }
}

Solution: Use @Order or implement Ordered:

@Component
@Order(1) // Runs first
class DatabaseInitializer implements CommandLineRunner {
    @Override
    public void run(String... args) {
        initializeDatabase();
    }
}

@Component
@Order(2) // Runs second
class DataLoader implements CommandLineRunner {
    @Override
    public void run(String... args) {
        loadData();
    }
}

// Or implement Ordered
@Component
class DatabaseInitializer implements CommandLineRunner, Ordered {
    @Override
    public void run(String... args) {
        initializeDatabase();
    }

    @Override
    public int getOrder() {
        return 1;
    }
}

Best Practice: Always specify order for interdependent runners. Lower numbers execute first.

9. Exit Code Not Propagated to System.exit()

Problem: Generating exit code but not propagating it to JVM.

// WRONG - exit code generated but JVM exits with 0
@SpringBootApplication
public class MyApp {
    public static void main(String[] args) {
        ConfigurableApplicationContext context =
            SpringApplication.run(MyApp.class, args);

        int exitCode = SpringApplication.exit(context, () -> 1);
        // Missing: System.exit(exitCode);
    }
}

@Component
class FailingService implements ExitCodeGenerator {
    @Override
    public int getExitCode() {
        return 1; // Indicates failure
    }
}

Solution: Always call System.exit() with the generated exit code:

// CORRECT
@SpringBootApplication
public class MyApp {
    public static void main(String[] args) {
        ConfigurableApplicationContext context =
            SpringApplication.run(MyApp.class, args);

        int exitCode = SpringApplication.exit(context);
        System.exit(exitCode); // Propagate to JVM
    }
}

// Better: Handle in one line
public static void main(String[] args) {
    System.exit(SpringApplication.exit(
        SpringApplication.run(MyApp.class, args)
    ));
}

Why It Matters: Without System.exit(), the JVM always exits with code 0, making failure detection impossible in scripts and CI/CD.

10. Modifying Environment After Context Refresh

Problem: Changing environment properties after context has been refreshed.

@SpringBootApplication
public class MyApp implements ApplicationRunner {
    @Autowired
    private ConfigurableEnvironment environment;

    @Override
    public void run(ApplicationArguments args) {
        // TOO LATE - beans already created with old property values
        environment.getSystemProperties().put("app.name", "NewName");
    }
}

Solution: Modify environment before refresh using EnvironmentPostProcessor or ApplicationContextInitializer:

// CORRECT - using EnvironmentPostProcessor
public class PropertyModifier implements EnvironmentPostProcessor {
    @Override
    public void postProcessEnvironment(
            ConfigurableEnvironment environment,
            SpringApplication application) {
        // Runs BEFORE beans are created
        Map<String, Object> props = new HashMap<>();
        props.put("app.name", "NewName");
        environment.getPropertySources().addFirst(
            new MapPropertySource("custom", props)
        );
    }
}

// Register in META-INF/spring.factories
// org.springframework.boot.env.EnvironmentPostProcessor=\
//   com.example.PropertyModifier

// Or use ApplicationContextInitializer
public class EnvInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext context) {
        ConfigurableEnvironment env = context.getEnvironment();
        // Modify environment before refresh
        env.getSystemProperties().put("app.name", "NewName");
    }
}

// Add to application
SpringApplication app = new SpringApplication(MyApp.class);
app.addInitializers(new EnvInitializer());
app.run(args);

Key Insight: Property changes only affect beans created after the change. Most beans are created during context refresh, so changes must happen before that.

AOT Support Classes

AotInitializerNotFoundException

Exception thrown when the AOT initializer class cannot be found during AOT-enabled startup.

package org.springframework.boot;

/**
 * Exception thrown when the AOT initializer couldn't be found.
 * Occurs during startup with AOT mode enabled when the expected AOT-generated
 * initializer class is not present in the classpath.
 *
 * Thread Safety: Immutable exception, safe to throw from any thread.
 *
 * @since 3.2.6
 */
public class AotInitializerNotFoundException extends RuntimeException {

    /**
     * Create a new exception for a missing AOT initializer.
     *
     * @param mainClass the main application class
     * @param initializerClassName the name of the expected initializer class that was not found
     */
    public AotInitializerNotFoundException(Class<?> mainClass, String initializerClassName);

    /**
     * Return the main application class.
     *
     * @return the main class
     */
    public Class<?> getMainClass();
}

When This Occurs:

  • AOT processing was enabled but the generated initializer class is missing
  • Application was built with AOT support but the AOT artifacts weren't included
  • Class loading issues prevent finding the AOT initializer

Example:

// This exception is thrown internally by Spring Boot when:
// 1. Application starts with AOT mode enabled
// 2. Expected initializer class like "com.example.MyApplication__ApplicationContextInitializer" is not found

// Typically indicates build configuration issue - AOT plugin didn't run or output wasn't included

SpringApplicationAotProcessor

Entry point for AOT (Ahead-of-Time) processing of a Spring Boot application. Generates optimized startup code and configuration during build time.

package org.springframework.boot;

import org.springframework.context.aot.ContextAotProcessor;

/**
 * Entry point for AOT processing of a SpringApplication.
 * Extends Spring Framework's ContextAotProcessor to provide Spring Boot-specific
 * AOT processing capabilities.
 *
 * This class is for internal use only and is typically invoked by build tools
 * during the AOT compilation phase.
 *
 * Thread Safety: Not thread-safe. Should only be used from build tool context.
 *
 * @since 3.0.0
 */
public class SpringApplicationAotProcessor extends ContextAotProcessor {

    /**
     * Create a new processor for the specified application and settings.
     *
     * @param application the application main class to process
     * @param settings the general AOT processor settings (output paths, artifact IDs)
     * @param applicationArgs the arguments to provide to the main method during processing
     */
    public SpringApplicationAotProcessor(Class<?> application, Settings settings, String[] applicationArgs);

    /**
     * Prepare the application context for AOT processing.
     * Invokes the application's main method to bootstrap the context, then
     * captures it before full startup completes.
     *
     * @param application the application main class
     * @return the prepared GenericApplicationContext ready for AOT processing
     */
    @Override
    protected GenericApplicationContext prepareApplicationContext(Class<?> application);

    /**
     * Main entry point for command-line invocation during build.
     * Expected arguments:
     * 1. applicationMainClass - fully qualified main class name
     * 2. sourceOutput - path for generated source files
     * 3. resourceOutput - path for generated resource files
     * 4. classOutput - path for generated class files
     * 5. groupId - Maven/Gradle group ID
     * 6. artifactId - Maven/Gradle artifact ID
     * 7+ originalArgs - optional application arguments
     *
     * @param args command line arguments
     * @throws Exception if AOT processing fails
     */
    public static void main(String[] args) throws Exception;
}

Usage Context: This processor is invoked automatically by Spring Boot build plugins:

  • Maven: spring-boot-maven-plugin process-aot goal
  • Gradle: org.springframework.boot plugin's processAot task

AOT Processing Flow:

  1. Build tool invokes SpringApplicationAotProcessor.main()
  2. Processor loads the application's main class
  3. Executes main method with special hook to capture ApplicationContext
  4. Generates optimized initialization code to sourceOutput
  5. Generates configuration metadata to resourceOutput
  6. Compiles generated sources to classOutput

Generated Artifacts:

  • __ApplicationContextInitializer.java - Context initialization code
  • __BeanDefinitions.java - Bean definition registrations
  • __BeanFactoryRegistrations.java - Bean factory configurations
  • META-INF/native-image/ - GraalVM native image configuration

Example Maven Configuration:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <executions>
        <execution>
            <goals>
                <goal>process-aot</goal>
            </goals>
        </execution>
    </executions>
</plugin>

See Also

  • Application Lifecycle Events
  • Configuration Properties
  • Spring Boot Reference - SpringApplication