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.
notification-client.jar
provides EmailClient
notification-spring-boot-autoconfigure.jar
provides NotificationAutoConfiguration
notification-spring-boot-starter
depends on both jars for convenienceA 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:
META-INF/spring/
└── org.springframework.boot.autoconfigure.AutoConfiguration.importscom.example.notification.NotificationAutoConfigurationThe 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.
notification-spring-boot-autoconfigure/
└── src/main/
├── java/com/example/notification/
│ └── NotificationAutoConfiguration.java
└── resources/META-INF/spring/
└── org.springframework.boot.autoconfigure.AutoConfiguration.imports- 01
The application enables auto-configuration
@SpringBootApplicationincludes that opt-in. - 02
Boot reads candidate imports
Every participating library can contribute configuration class names.
- 03
Conditions filter candidates and bean methods
Unavailable or unwanted configurations do not contribute definitions.
- 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.
@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.
createDefaultSender =
emailClientIsPresent
&& propertyAllowsNotifications
&& noNotificationSenderBeanExists;| EmailClient | enabled property | Existing sender | Create default email? |
|---|---|---|---|
| Present | Missing | No | Yes |
| Present | true | No | Yes |
| Present | false | No | No: property condition fails. |
| Present | Missing | SMS bean | No: missing-bean condition fails. |
| Absent | true | No | No: class condition fails. |
Walk through four startups
The same auto-configuration can produce different contexts.
Library present, property missing, no user bean
All conditions match. The context receives one default EmailNotificationSender.
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.
Library present, property missing, user supplies SMS
The missing-bean condition fails. The default email definition backs off, leaving the user's SMS implementation.
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.
@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.
| Question | Component scanning | Auto-configuration |
|---|---|---|
| Who normally owns the class? | The application | A framework or reusable library |
| How is it discovered? | Package scan below the application root | AutoConfiguration.imports |
| What usually controls registration? | Component stereotype and scan boundary | Classpath, bean, property and other conditions |
| Typical example | OrderService | Default 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.
Positive matches:
NotificationAutoConfiguration
- EmailClient was found
- orders.notification.enabled was missing
and matchIfMissing was true
Negative matches:
notificationSender
- NotificationSender bean already existedA 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.
Write an auto-configuration that creates an email NotificationSender only when:
EmailClientis on the classpath.- No
NotificationSenderbean already exists. orders.notification.enabledis 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
- Put the classpath condition on the auto-configuration class.
- Put the missing-bean and property conditions on the bean method.
- All conditions are combined with logical AND.
- A user-defined
SmsNotificationSenderstill counts as aNotificationSenderbean.
Working solution
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();
}
}com.example.notification.NotificationAutoConfigurationScenario result
- OnClass: matches because
EmailClientis present. - OnProperty: matches because the property is missing and
matchIfMissing = true. - 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.
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.