Learnitweb

How Spring Boot Auto-Configuration Internally Uses @ConditionalOnClass and @ConditionalOnMissingBean

Spring Boot’s auto-configuration mechanism simplifies application setup by automatically configuring beans based on the presence of classes, beans, or properties. Two key annotations that power this mechanism are:

  • @ConditionalOnClass
  • @ConditionalOnMissingBean

Understanding how these work is critical to mastering Spring Boot internals and customizing behavior effectively.

1. What is Auto-Configuration in Spring Boot?

Spring Boot provides opinionated defaults by scanning the classpath and application context and automatically configuring components if certain conditions are met.

For example:

  • If spring-boot-starter-web is on the classpath, it auto-configures DispatcherServlet, Jackson, Tomcat, etc.
  • If a developer defines their own ObjectMapper bean, the default one is not created.

This is made possible using conditional annotations inside auto-configuration classes.

2. Introduction to @ConditionalOnClass

Used to conditionally load a bean or configuration only if a specific class is present on the classpath.

Typical Usage:

@Configuration
@ConditionalOnClass(name = "com.fasterxml.jackson.databind.ObjectMapper")
public class JacksonAutoConfiguration {
    @Bean
    public ObjectMapper objectMapper() {
        return new ObjectMapper();
    }
}

How it Works:

  • When Spring Boot starts, it checks whether the specified class (ObjectMapper) is present in the classpath.
  • If it’s not present, the JacksonAutoConfiguration class is skipped entirely.

Use Case:

  • Avoid configuring unused features
  • Only configure if dependencies are present (e.g., don’t configure MongoDB beans if MongoDB driver is missing)

3. Introduction to @ConditionalOnMissingBean

Prevents Spring Boot from creating a bean if one already exists in the application context.

Typical Usage:

@Bean
@ConditionalOnMissingBean
public ObjectMapper objectMapper() {
    return new ObjectMapper();
}

How it Works:

  • Before registering the ObjectMapper bean, Spring checks whether any other ObjectMapper bean is already defined.
  • If the developer already defined one, Spring skips this bean.
  • If no ObjectMapper bean exists, Spring will register this one.

Use Case:

  • Allow developers to override default beans
  • Avoid bean conflicts in application context

4. How These Two Work Together in Auto-Configuration

Auto-configuration classes combine these annotations to provide flexible, safe, and conditional bean creation.

Example: Jackson Auto-Configuration (Simplified)

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ObjectMapper.class)  // Load only if Jackson is on classpath
public class JacksonAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(ObjectMapper.class) // Only create if not already defined
    public ObjectMapper objectMapper() {
        return new ObjectMapper();
    }
}

Flow:

  1. Spring Boot sees @ConditionalOnClass(ObjectMapper.class) and checks if Jackson is in classpath.
  2. If present, it proceeds to register the configuration class.
  3. Then it evaluates the bean definition:
    • If the developer already defined an ObjectMapper bean → Spring skips it.
    • If not → Spring creates and registers the default one.

This provides out-of-the-box functionality, while still allowing custom overrides.

5. How Auto-Configuration Classes Are Loaded

The role of spring.factories or META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

Auto-configuration classes are listed in:

  • For Spring Boot < 2.7: META-INF/spring.factories
  • For Spring Boot 3.x+: META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

Example entry:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.autoconfigure.MyAutoConfiguration

When Spring Boot starts:

  • It scans the auto-configuration entries.
  • Loads each class.
  • Applies the conditional annotations to decide whether to apply it.

6. Internals: Conditional Annotations via @Conditional

All @ConditionalOnXxx annotations are meta-annotations on @Conditional.

Let’s see how:

@ConditionalOnClass is defined as:

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
    Class<?>[] value() default {};
    String[] name() default {};
}

@ConditionalOnMissingBean is defined as:

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnMissingBean {
    Class<?>[] value() default {};
}

hese annotations delegate their logic to:

  • OnClassCondition → checks classpath
  • OnBeanCondition → checks the ApplicationContext for beans

7. Visual Flow of Spring Boot Auto-Configuration

                Application Startup
                        │
            ┌──────────▼────────────┐
            │ Scan AutoConfig Imports│
            └──────────┬────────────┘
                       ▼
           For Each Auto-Config Class:
                       │
         ┌─────────────┼─────────────────────┐
         ▼             ▼                     ▼
Check @ConditionalOnClass   Check @ConditionalOnMissingBean
         │             │                     │
     If true       If true             Create and register Bean
       │              │
       ▼              ▼
  Apply Configuration

8. Example: Creating Your Own Auto-Configuration with Conditions

Step 1: Custom Auto-Config Class

@Configuration
@ConditionalOnClass(MyService.class)
public class MyServiceAutoConfiguration {

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

Step 2: Register in spring.factories (Spring Boot < 3.0)

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.autoconfig.MyServiceAutoConfiguration

Step 3: Use in Application

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

If you don’t define MyService, it’s auto-configured. If you define it manually, auto-configuration backs off.