← Series overview

Spring Boot from first principles · Module 05 of 05

Let Boot contribute defaults that back off cleanly.

Auto-configuration is conditional configuration supplied by libraries. It observes the classpath, existing beans and properties, then contributes only the definitions whose conditions match.

After this module, you should be able to:
  • distinguish a library, a starter and an auto-configuration
  • explain how auto-configuration candidates are discovered
  • evaluate class, bean and property conditions independently
  • predict when a user-defined bean makes Boot back off

Three different pieces

A starter brings dependencies; auto-configuration creates conditional beans.

Library

Provides classes and behaviour, such as a database driver or email client. Adding a library does not necessarily create any Spring beans.

Starter

A dependency descriptor that brings together a compatible set of libraries for one capability.

Auto-configuration

Configuration classes that create useful default beans when their conditions match.

This is why adding one dependency can change startup behaviour: a class that was absent is now present, so a classpath condition may begin to match.

One capability, three layers
notification-client.jar
    provides EmailClient

notification-spring-boot-autoconfigure.jar
    provides NotificationAutoConfiguration

notification-spring-boot-starter
    depends on both jars for convenience

A project can use the client library manually without Spring. It can use the auto-configuration with explicitly chosen dependencies. The starter simply makes the common combination easy to declare.

Candidate discovery

Auto-configuration is imported, not found by your component scan.

@EnableAutoConfiguration, included by @SpringBootApplication, asks Boot to load candidate configuration classes. A reusable library lists those candidates in:

AutoConfiguration.imports
META-INF/spring/
└── org.springframework.boot.autoconfigure.AutoConfiguration.imports
File contents
com.example.notification.NotificationAutoConfiguration

The configuration class is marked @AutoConfiguration. Merely placing it under com.example.orders is not the registration mechanism for a reusable auto-configuration.

This matters because library authors cannot predict the package of every consuming application. Package scanning is controlled by the application; the imports file is controlled by the library and travels inside its jar.

Typical reusable module layout
notification-spring-boot-autoconfigure/
└── src/main/
    ├── java/com/example/notification/
    │   └── NotificationAutoConfiguration.java
    └── resources/META-INF/spring/
        └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
  1. 01

    The application enables auto-configuration

    @SpringBootApplication includes that opt-in.

  2. 02

    Boot reads candidate imports

    Every participating library can contribute configuration class names.

  3. 03

    Conditions filter candidates and bean methods

    Unavailable or unwanted configurations do not contribute definitions.

  4. 04

    Matching definitions join the same context

    Application components and auto-configured infrastructure are wired together through normal dependency injection.

Conditions

Every declared condition must match.

@ConditionalOnClass

Create the configuration only when a required library class is available.

@ConditionalOnMissingBean

Provide a default only when the application has not already supplied that bean type.

@ConditionalOnProperty

Enable or disable configuration from an external property and its missing-value rule.

A conditional default
@Bean
@ConditionalOnMissingBean(NotificationSender.class)
@ConditionalOnProperty(
        prefix = "orders.notification",
        name = "enabled",
        havingValue = "true",
        matchIfMissing = true
)
public NotificationSender notificationSender() {
    return new EmailNotificationSender();
}

Here, true matches, false does not, and a missing property also matches because matchIfMissing is true.

Read conditions as predicates. The class-level condition controls whether the configuration is relevant at all. Method-level conditions control whether one particular bean definition should be added.

The combined decision
createDefaultSender =
        emailClientIsPresent
        && propertyAllowsNotifications
        && noNotificationSenderBeanExists;
EmailClientenabled propertyExisting senderCreate default email?
PresentMissingNoYes
PresenttrueNoYes
PresentfalseNoNo: property condition fails.
PresentMissingSMS beanNo: missing-bean condition fails.
AbsenttrueNoNo: class condition fails.

Walk through four startups

The same auto-configuration can produce different contexts.

Scenario A

Library present, property missing, no user bean

All conditions match. The context receives one default EmailNotificationSender.

Scenario B

Library present, property set to false, no user bean

The property condition fails. The auto-configuration creates no sender. If another bean requires NotificationSender, that separate dependency may now make startup fail.

Scenario C

Library present, property missing, user supplies SMS

The missing-bean condition fails. The default email definition backs off, leaving the user's SMS implementation.

Scenario D

Email client library absent

The class condition prevents this configuration from applying. Other notification auto-configurations could still be evaluated independently.

Back-off

User configuration has the final say.

@ConditionalOnMissingBean is the central back-off mechanism. The auto-configuration offers a useful default, while an application can replace that default simply by defining its own bean. No framework source code needs to be edited.

The consuming application overrides the default
@Configuration(proxyBeanMethods = false)
class ApplicationNotificationConfiguration {

    @Bean
    NotificationSender notificationSender() {
        return new SmsNotificationSender();
    }
}

The application does not call or subclass the auto-configuration. Its bean definition is visible when the conditional default is evaluated, so@ConditionalOnMissingBean does not match.

Back-off is more maintainable than “create both and mark one primary.” It prevents an unwanted default from existing at all and lets a reusable library remain opinionated without becoming rigid.

Component scan versus auto-configuration

Both register beans, but they discover definitions differently.

QuestionComponent scanningAuto-configuration
Who normally owns the class?The applicationA framework or reusable library
How is it discovered?Package scan below the application rootAutoConfiguration.imports
What usually controls registration?Component stereotype and scan boundaryClasspath, bean, property and other conditions
Typical exampleOrderServiceDefault data source or client infrastructure

Once registered, both kinds of definitions participate in the same container. Constructor injection does not care whether a bean originated from scanning, an application @Bean method or auto-configuration.

Diagnosing decisions

The condition evaluation report explains why configurations matched.

When startup behaviour is surprising, run with --debug or set debug=true. The condition evaluation report shows positive and negative matches and the reason for each decision.

Simplified report reasoning
Positive matches:
  NotificationAutoConfiguration
    - EmailClient was found
    - orders.notification.enabled was missing
      and matchIfMissing was true

Negative matches:
  notificationSender
    - NotificationSender bean already existed

A negative match is not necessarily an error. Back-off intentionally appears as a negative match. Read the reason before excluding an auto-configuration or adding annotations to force it.

Common debugging mistake

Seeing that a class is on the classpath and assuming its bean must exist. Class presence may satisfy only one of several conditions.

Practice checkpoint

Write it before reading the solution.

Suggested time: 12 minutes

Write an auto-configuration that creates an email NotificationSender only when:

  1. EmailClient is on the classpath.
  2. No NotificationSender bean already exists.
  3. orders.notification.enabled is true or missing.

Then evaluate this scenario: EmailClient is present, the property is missing, and the application defines an SMS bean that implements NotificationSender. State each condition result and the final beans in the context.

Hints, in increasing order
  1. Put the classpath condition on the auto-configuration class.
  2. Put the missing-bean and property conditions on the bean method.
  3. All conditions are combined with logical AND.
  4. A user-defined SmsNotificationSender still counts as a NotificationSender bean.
Working solution
NotificationAutoConfiguration.java
package com.example.notification;

import com.example.email.EmailClient;

import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;

@AutoConfiguration
@ConditionalOnClass(EmailClient.class)
public class NotificationAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(NotificationSender.class)
    @ConditionalOnProperty(
            prefix = "orders.notification",
            name = "enabled",
            havingValue = "true",
            matchIfMissing = true
    )
    public NotificationSender notificationSender() {
        return new EmailNotificationSender();
    }
}
AutoConfiguration.imports
com.example.notification.NotificationAutoConfiguration

Scenario result

  1. OnClass: matches because EmailClient is present.
  2. OnProperty: matches because the property is missing and matchIfMissing = true.
  3. OnMissingBean: does not match because the user's SMS bean implements NotificationSender.

The conditions are ANDed, so the email bean is not created. The final context contains the user-defined SMS NotificationSender, not both implementations.

Record after the attempt

Elapsed time · hint level (H0 means no hint; H1–Hn is the last hint used) · conceptual errors · mechanical errors · explanation confidence from 0–2.

Foundation check

Component scanning and auto-configuration solve different discovery problems.

Component scanning finds your annotated application classes below a package root. Auto-configuration imports library-provided candidates and then filters them with conditions. Both can contribute beans to the same context.