9. Runner Interfaces & Startup Pipeline

End-to-End Configuration Pipeline & Pitfalls

Master the complete Spring Boot environment-to-runner execution pipeline, complexity trade-offs, production pitfalls, end-to-end code examples, and self-assessment challenges.

End-to-End Configuration Pipeline & Pitfalls

Understanding how external configuration flows through the Environment, binds to beans, and triggers post-boot execution completes the Spring Boot configuration masterclass.


1. End-to-End Architecture Data Flow

1. Application Startup SpringApplication.run() 2. Spring Environment Creation application.properties application.yml OS Environment Variables System Properties Command-Line Arguments 3. Resolved Configuration @Value Injection @ConfigurationProperties Binding Spring Beans (Services / Repositories) 4. ApplicationContext Bootstrapping Component Scanning Auto-Configuration Bean Instantiation Dependency Injection 5. Startup Runners CommandLineRunner / ApplicationRunner 6. Execute Application Startup Logic

2. Configuration-to-Bean Execution Pipeline

Configuration Sources (properties, yml, env, args)


Spring Environment (Central Property Resolver)

       ├─────────────────────────────────┐
       ▼                                 ▼
   @Value                       @ConfigurationProperties
(Injects single field)          (Binds grouped namespace object)
       │                                 │
       └────────────────┬────────────────┘

            Spring Beans (Services)


       Application Context Ready

            ┌───────────┴───────────┐
            ▼                       ▼
   CommandLineRunner        ApplicationRunner
            │                       │
            └───────────┬───────────┘

              Execute Startup Logic

3. Complexity & Engineering Trade-Off Matrix

Scenario / ComplexityRecommended Injection ApproachArchitectural Reason
1–2 Simple Flags (e.g. payment.enabled=true)@ValueDirect, minimal boilerplate for single standalone values.
4+ Related Properties (e.g. timeout, retry, url, key)@ConfigurationPropertiesModels a cohesive domain configuration object.
Relaxed Naming Needs (PAYMENT_RETRY_COUNT in Docker)@ConfigurationPropertiesFull support for camelCase to UPPERCASE relaxed binding.
Shared Settings Across 5 Services@ConfigurationPropertiesInject single properties bean into all 5 services without repeating @Value.
Simple CLI ScriptCommandLineRunnerSimple raw String... args processing.
Enterprise CLI Tool with --flagsApplicationRunnerNative support for structured ApplicationArguments.

4. Production Pitfalls Checklist

Pitfall 1: Assuming src/main/resources/application.properties Is Truly External

  • Mistake: Thinking changing application.properties in project source updates a deployed production JAR.
  • Fix: Use true external configuration (OS Environment Variables or --server.port=8081 flags) for production overrides.

Pitfall 2: Excessive @Value Proliferation

  • Mistake: Adding 20 @Value fields across multiple classes, scattering configuration keys.
  • Fix: Refactor related settings into a single @ConfigurationProperties(prefix = "...") bean.

Pitfall 3: Missing Required Configuration

  • Mistake: Using @Value("${payment.provider}") without a default value when the property might be missing.
  • Fix: Provide a safe fallback: @Value("${payment.provider:DefaultProvider}").

Pitfall 4: Manual Bean Retrieval in main()

  • Mistake: Calling context.getBean(MyService.class) inside main().
  • Fix: Implement CommandLineRunner or ApplicationRunner to let Spring manage execution via DI.

5. Comprehensive End-to-End Code Example

1. application.properties

payment.provider=Razorpay
payment.retry-count=3
payment.enabled=true
payment.timeout=5000

2. Grouped Configuration Object

@Component
@ConfigurationProperties(prefix = "payment")
public class PaymentProperties {

    private String provider;
    private int retryCount;
    private boolean enabled;
    private int timeout;

    // Getters and Setters
    public String getProvider() { return provider; }
    public void setProvider(String provider) { this.provider = provider; }

    public int getRetryCount() { return retryCount; }
    public void setRetryCount(int retryCount) { this.retryCount = retryCount; }

    public boolean isEnabled() { return enabled; }
    public void setEnabled(boolean enabled) { this.enabled = enabled; }

    public int getTimeout() { return timeout; }
    public void setTimeout(int timeout) { this.timeout = timeout; }
}

3. Business Service

@Component
public class PaymentService {

    private final PaymentProperties properties;

    public PaymentService(PaymentProperties properties) {
        this.properties = properties;
    }

    public void pay() {
        if (!properties.isEnabled()) {
            System.out.println("Payment disabled.");
            return;
        }

        System.out.println("Payment provider: " + properties.getProvider());
        System.out.println("Retry count: " + properties.getRetryCount());
        System.out.println("Timeout: " + properties.getTimeout() + "ms");
    }
}

4. Post-Boot Startup Runner

@Component
public class AppRunner implements CommandLineRunner {

    private final PaymentService paymentService;

    public AppRunner(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    @Override
    public void run(String... args) {
        System.out.println("=== Spring Boot Bootstrapped Successfully ===");
        paymentService.pay();
    }
}

❓ Interactive Self-Assessment

Knowledge Check

A Spring Boot app has payment.provider, payment.retry-count, payment.enabled, and payment.timeout. Which design is most scalable?

Knowledge Check

What is the key difference between CommandLineRunner and ApplicationRunner?

Refactoring Scattered @Value Injections

A legacy application has 4 scattered @Value injections in PaymentService. Refactor this design using @ConfigurationProperties.

On this page