7. Auto-Configuration & Conditional Mechanics

Conditional Beans & Production Diagnostics

Master @ConditionalOnClass and @ConditionalOnMissingBean, developer vs auto-configured bean precedence, full startup lifecycle state machines, 5 diagnostic debugging questions, and anti-patterns.

Conditional Beans & Production Diagnostics

Auto-configuration relies on Conditional Annotations to inspect container state and classpath availability before instantiating beans.


1. Conditional Annotations: @ConditionalOnClass & @ConditionalOnMissingBean

A simplified Spring Boot auto-configuration class looks like:

@AutoConfiguration
@ConditionalOnClass(PaymentGateway.class) // Condition 1: Class must exist on classpath
public class PaymentAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean // Condition 2: Register ONLY IF developer supplied no custom bean
    public PaymentGateway paymentGateway() {
        return new DefaultPaymentGateway();
    }
}
Decision Tree Execution:
Is PaymentGateway.class available on Classpath?

        ├── NO  ──► Skip this entire Configuration class.

        └── YES


      Does a PaymentGateway Bean ALREADY exist in ApplicationContext?

             ├── YES ──► Yield precedence to developer's custom bean!

             └── NO  ──► Register DefaultPaymentGateway bean.

@ConditionalOnClass (Classpath Check)

@ConditionalOnClass guarantees that an auto-configuration blueprint activates only if specified third-party classes exist on the runtime classpath.

@ConditionalOnClass(name = "org.postgresql.Driver")
public class PostgresAutoConfiguration { ... }

@ConditionalOnMissingBean (Customization Hook)

@ConditionalOnMissingBean allows developers to override Spring Boot's defaults effortlessly.

If you do nothing, Spring Boot registers DefaultPaymentGateway. If you declare your own custom bean:

@Configuration
public class CustomPaymentConfig {

    @Bean
    public PaymentGateway paymentGateway() {
        return new CustomStripePaymentGateway(); // Custom Developer Bean
    }
}

Because your custom bean exists in ApplicationContext, @ConditionalOnMissingBean evaluates to FALSE, and Spring Boot quietly skips instantiating DefaultPaymentGateway!


2. Developer Beans vs Auto-Configured Beans

PropertyDeveloper-Defined BeanAuto-Configured Bean
SourceWritten by application developer in project code.Provided by Spring Boot or third-party starters.
DiscoveryComponent scanning (@Component, @Service) or @Configuration.Pre-written @AutoConfiguration blueprints.
ActivationExplicitly declared.Condition-driven (@ConditionalOnClass, etc.).
PrecedenceHigh Precedence (Overrides defaults).Fallback Precedence (Controlled by @ConditionalOnMissingBean).

3. Complete Startup Lifecycle Architecture

SpringApplication.run(MyApplication.class) Create ApplicationContext & Environment Process Main Configuration Root Execute Component Scan (Discover @Component, @Service) Return Developer Bean Definitions Evaluate Auto-Configurations against Classpath & Beans Return Conditional Infrastructure Bean Definitions Instantiate Beans & Inject Dependencies ApplicationContext Ready Execute CommandLineRunner.run(args) Startup Hooks Complete JVM main() SpringApplication ApplicationContext Component Scanner Auto-Configuration Engine CommandLineRunner

Startup State Machine

Lifecycle StateDescriptionPrimary Operational Task
STARTINGJVM invokes main()Application initialization.
BOOTSTRAPPINGSpringApplication.run() calledEnvironment and banner setup.
CONTEXT_CREATINGApplicationContext constructedResource allocation.
DISCOVERYComponent scanning executedPackage tree inspection for @Component.
AUTO_CONFIGURATIONConditions evaluated@ConditionalOnClass & @ConditionalOnMissingBean checks.
BEAN_CREATIONInstantiation & WiringConstructor execution & Dependency Injection.
READYContext fully operationalSocket listener startup (Tomcat port 8080).
RUNNERSPost-boot hooks executedCommandLineRunner.run() invocation.

4. Production Debugging Mental Model: The 5 Questions

When an infrastructure bean or custom component is unexpectedly missing or failing to configure, reason through these 5 Diagnostic Questions:

Production Debugging Steps:
├── Q1. Is the Maven dependency present in pom.xml? (Check runtime classpath)
├── Q2. Is the custom component inside the package scan tree? (Check root package hierarchy)
├── Q3. Is the relevant Auto-Configuration class triggered? (Check condition evaluations)
├── Q4. Did another custom bean satisfy @ConditionalOnMissingBean first? (Check bean overrides)
└── Q5. Are application.properties settings enabling/disabling the feature? (Check property toggles)

5. Common Production Anti-Patterns

Anti-Pattern 1: Main Class Buried in Deep Sub-Package

  • Placing MyApplication.java in com.company.app.bootstrap instead of root com.company.app, causing component scanning to miss services in sibling packages.

Anti-Pattern 2: Using ApplicationContext as a Global Service Locator

  • Injecting ApplicationContext to call context.getBean(MyService.class) inside methods instead of using Constructor Dependency Injection.

Anti-Pattern 3: Treating Starters as Single Jar Libraries

  • Assuming spring-boot-starter-web is one file, rather than a curated dependency bundle that shifts the entire runtime classpath.

Anti-Pattern 4: Assuming Spring Boot Always Starts a Web Server

  • Assuming @SpringBootApplication always opens port 8080, ignoring that web server auto-configuration activates only when web starter dependencies exist on the classpath.

6. Comprehensive Code Example

package com.company.orders;

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

@SpringBootApplication // Root package scan starts here!
public class OrdersApplication {

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

@Service
class PaymentService {
    public void processPayment() {
        System.out.println("Processing payment via PaymentService...");
    }
}

@Service
class OrderService {
    private final PaymentService paymentService;

    // Clean Constructor Injection
    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService;
    }

    public void placeOrder() {
        paymentService.processPayment();
        System.out.println("Order successfully placed!");
    }
}

@Component
class StartupRunner implements CommandLineRunner {
    private final OrderService orderService;

    public StartupRunner(OrderService orderService) {
        this.orderService = orderService;
    }

    @Override
    public void run(String... args) {
        System.out.println("=== StartupRunner Executing ===");
        orderService.placeOrder();
    }
}

❓ Interactive Self-Assessment

Knowledge Check

What happens when a custom developer bean matches a Spring Boot default bean annotated with @ConditionalOnMissingBean?

Knowledge Check

Why is placing the main @SpringBootApplication class in the root package (e.g., com.company.app) an architectural best practice?

Refactoring Service Locator Anti-Pattern

Refactor a legacy Spring Boot class that injects ApplicationContext to manually fetch PaymentService via context.getBean() into clean Constructor Injection.

On this page