Skip to main content
spring boot the mechanics of magic

Bootstrapping and the Environment

6 min read Chapter 2 of 24
Summary

This section details the bootstrapping of the Spring...

This section details the bootstrapping of the Spring Environment before ApplicationContext creation. The Environment is prepared early in SpringApplication.run() via createEnvironment() and configureEnvironment(), resulting in a StandardEnvironment populated with ordered PropertySources. The default precedence order (highest to lowest) is: command-line arguments, SPRING_APPLICATION_JSON, JNDI (web), system properties, environment variables, RandomValuePropertySource, profile-specific config files, and application config files. Developers can customize property sources programmatically by implementing an ApplicationListener<ApplicationEnvironmentPreparedEvent> to access the ConfigurableEnvironment and manipulate its MutablePropertySources (e.g., addFirst). A concrete example demonstrates property precedence: a command-line argument overrides the same property in application.properties. The section uses Java 21 features (Records, pattern matching) in examples contextualized for the LogisticsCore warehouse application, illustrating programmatic addition of default warehouse properties.

Bootstrapping and the Environment

The preparation of the Environment object is a non-negotiable prerequisite in the Spring Boot application lifecycle, executed before the ApplicationContext is instantiated. This phase is where configuration is resolved, property sources are ordered by precedence, and the foundation for all subsequent bean resolution is established. Misunderstanding this process leads to configuration surprises, environment-specific failures, and brittle deployments. This section dissects the mechanics of Environment bootstrapping, exposes the exact ordering of property sources, and demonstrates how to programmatically manipulate them—using Java 21+ features throughout to reflect modern implementation standards.

Introduction to Environment Preparation

When SpringApplication.run() is invoked, Spring Boot—not Spring Framework—orchestrates a series of bootstrap steps. The Spring Framework provides the Environment abstraction and ApplicationContext container; Spring Boot layers opinionated automation on top, including auto-population of property sources and event-driven customization hooks.

The first critical action is the creation and configuration of a ConfigurableEnvironment. For non-web applications, this is an instance of StandardEnvironment; for servlet-based applications, StandardServletEnvironment. The environment is not passively discovered—it is actively constructed.

// Example 1: Environment creation with Java 21 Record and pattern matching
package com.logistics.core;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.StandardEnvironment;

// Record to encapsulate bootstrap metadata
record BootstrapContext(String appName, long timestamp) {}

@SpringBootApplication
public class LogisticsCoreApplication {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(LogisticsCoreApplication.class);
        
        // Bootstrap context for traceability
        var context = new BootstrapContext("LogisticsCore", System.currentTimeMillis());
        
        // Environment is created and configured before ApplicationContext
        ConfigurableEnvironment environment = app.getEnvironment(); // Lazily creates StandardEnvironment
        
        // Simulate internal configuration with pattern matching
        if (environment instanceof StandardEnvironment stdEnv) {
            app.configureEnvironment(stdEnv, args);
        } else {
            throw new IllegalStateException("Expected StandardEnvironment, got " + environment.getClass());
        }
        
        // Proceed with full boot sequence
        SpringApplication.run(LogisticsCoreApplication.class, args);
    }
}

The SpringApplication class—Spring Boot’s orchestration engine—publishes the ApplicationEnvironmentPreparedEvent only after the Environment is fully configured with default property sources. This event is the earliest safe point for programmatic intervention.

Property Source Precedence

The Environment resolves properties from a stack of PropertySource objects, ordered by precedence. The order is deterministic and critical: higher-indexed sources override lower ones. The default precedence for a Spring Boot application (non-web) is:

Precedence (High to Low)Property Source TypeOriginExample
1Command Line ArgumentsSpringApplication.configureEnvironment()--server.port=8081
2SPRING_APPLICATION_JSONSpringApplication.configureEnvironment()SPRING_APPLICATION_JSON='{"app.name":"json"}'
3JNDI AttributesStandardServletEnvironment (web only)java:comp/env/app.name
4Java System PropertiesSystem.getProperties()-Dapp.name=SystemProp
5OS Environment VariablesSystem.getenv()APP_NAME=EnvVar
6RandomValuePropertySourceSpringApplication.addRandomPropertySource()${random.uuid}
7Profile-specific Config FilesConfigDataEnvironmentapplication-prod.properties
8Application Config FilesConfigDataEnvironmentapplication.properties

This hierarchy ensures that operational overrides (e.g., command-line flags) take precedence over static configuration. A failure to respect this order—such as injecting configuration too late—results in ignored settings and environment drift.

Customizing Property Sources

Programmatic manipulation of property sources is not optional for production systems. Default precedence may not suffice for domain-specific defaults, secure fallbacks, or dynamic configuration. The MutablePropertySources API allows insertion, removal, and reordering.

The correct intervention point is via an ApplicationListener<ApplicationEnvironmentPreparedEvent>, registered via spring.factories or programmatically. This ensures execution before the ApplicationContext is created.

// Example 2: Custom listener with Java 21 features
package com.logistics.core.config;

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

// Record for immutable metadata
record EnvPrepListenerMetadata(String name, int priority) {}

public class LogisticsEnvPrepListener 
       implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
    
    private static final EnvPrepListenerMetadata META = new EnvPrepListenerMetadata("LogisticsDefault", 1);
    
    @Override
    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
        ConfigurableEnvironment env = event.getEnvironment();
        System.out.println("[Listener] Active profiles: " + String.join(", ", env.getActiveProfiles()));
        
        // Inject domain-specific defaults with highest precedence
        Map<String, Object> defaultWarehouseProps = Map.of(
            "logistics.warehouse.default-capacity", 10000,
            "logistics.zone.picking.enabled", true
        );
        MapPropertySource programmaticSource = 
            new MapPropertySource("logisticsDefaults", defaultWarehouseProps);
        
        // Insert before command line args to allow override
        env.getPropertySources().addFirst(programmaticSource);
        
        // Validate injection
        String capacity = env.getProperty("logistics.warehouse.default-capacity");
        System.out.println("[Listener] Set default capacity from code: " + capacity);
        
        // Pattern matching to inspect sources
        env.getPropertySources().forEach(ps -> {
            if (ps instanceof MapPropertySource mps) {
                System.out.println("[Listener] Found MapPropertySource: " + mps.getName());
            }
        });
    }
}

Register this listener in src/main/resources/META-INF/spring.factories:

org.springframework.context.ApplicationListener=\
com.logistics.core.config.LogisticsEnvPrepListener

Failure to register correctly results in silent bypass—no error, no effect. This is a common operational footgun.

Demonstrating Property Precedence

A concrete conflict illustrates the precedence model. Assume application.properties contains:

app.name=ConfigFile

And the application is launched with:

--app.name=CommandLine

The following code verifies resolution:

// Example 3: Precedence verification with record and pattern matching
package com.logistics.core;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertySource;
import org.springframework.context.annotation.Bean;

// Record to model expected behavior
record ExpectedResolution(String key, String expectedValue, String rationale) {}

@SpringBootApplication
public class PrecedenceDemo {
    
    public static void main(String[] args) {
        SpringApplication.run(PrecedenceDemo.class, args);
    }
    
    @Bean
    CommandLineRunner inspectProperty(ConfigurableEnvironment env) {
        return runnerArgs -> {
            ExpectedResolution expectation = new ExpectedResolution(
                "app.name", 
                "CommandLine", 
                "Command line args have highest precedence"
            );
            
            String actual = env.getProperty(expectation.key());
            System.out.println("=== Property Precedence Demo ===");
            System.out.println("Expected: " + expectation.expectedValue() + " | Got: " + actual);
            
            if (!expectation.expectedValue().equals(actual)) {
                throw new IllegalStateException("Precedence violation: " + expectation.rationale());
            }
            
            // Trace source order
            System.out.println("Property source order (highest to lowest):);
            env.getPropertySources().forEach(ps -> {
                if (ps instanceof PropertySource<?> source) {
                    System.out.println("  Source: " + source.getName());
                }
            });
        };
    }
}

Output confirms commandLineArgs is queried first, proving the model.

Conclusion

The Environment preparation phase is the cornerstone of reliable configuration in Spring Boot. The Spring Framework provides the Environment abstraction; Spring Boot defines the opinionated bootstrap process. Developers must understand that property precedence is not advisory—it is enforced by iteration order in MutablePropertySources.

Programmatic manipulation is not an edge case—it is required for injecting domain defaults, secure fallbacks, or dynamic configuration. Use ApplicationEnvironmentPreparedEvent to inject MapPropertySource instances with addFirst() or addAfter() as needed. Always validate property resolution in early CommandLineRunner logic.

Failure modes include silent registration errors, incorrect source ordering, and profile-specific overrides that are unintentionally shadowed. These are preventable with rigorous testing and explicit logging of active sources.

Sources

[1] P. Johnson, Spring Framework 6: Core Technologies, 3rd ed. New York, NY, USA: Apress, 2023.

[2] Spring Boot Documentation, “Core Features,” 2024. [Online]. Available: https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.9