A Practical Guide to Writing Custom Spring Boot Auto-Configuration
Spring Boot auto-configuration lets a library configure itself when it detects the right classes, properties, or application conditions. A well-designed module can add useful defaults while allowing each application to override them cleanly.
This guide builds a small payment client starter, using patterns that apply to database adapters, authentication modules, messaging clients, and internal platform libraries. The examples suit teams shipping services in Sydney, Melbourne, Brisbane, or anywhere an application needs predictable configuration across environments.
| Approach | Best use | Main benefit | Common risk |
|---|---|---|---|
@ConditionalOnClass |
Optional integrations | Activates only when a dependency exists | Unexpected activation |
@ConditionalOnProperty |
Feature flags | Gives operators explicit control | Incorrect default value |
@ConditionalOnMissingBean |
User customisation | Preserves application-defined beans | Ambiguous bean ownership |
@ConfigurationProperties |
External settings | Type-safe configuration | Weak validation |
| Auto-configuration module | Reusable library defaults | Consistent setup across projects | Hidden behaviour |
Start With a Small Starter Module
Keep auto-configuration separate from the business implementation. A common Maven layout contains a client library, an auto-configuration module, and an optional starter module that collects dependencies.
The starter should remain thin. For example, a payment starter might depend on the HTTP client, the payment API library, and the auto-configuration module. This structure makes the feature easy to add to a Spring Boot service without forcing unrelated dependencies onto the classpath.
<dependency>
<groupId>com.javawhizz</groupId>
<artifactId>payment-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
Define Type-Safe Application Properties
Use @ConfigurationProperties for settings such as the provider URL, API key, connection timeout, and enabled state. This is clearer than scattering @Value expressions throughout configuration code.
@ConfigurationProperties(prefix = "javawhizz.payment")
public class PaymentProperties {
private boolean enabled = true;
private URI baseUrl = URI.create("https://api.example.com");
private Duration timeout = Duration.ofSeconds(5);
private String apiKey;
// getters and setters
}
Register the class with @EnableConfigurationProperties or @ConfigurationPropertiesScan. Add validation for production-sensitive values, and document the properties in the starter’s README. Australian teams commonly deploy through separate dev, staging, and production accounts, so externalised credentials are essential.
Add Conditions Before Creating Beans
A custom auto-configuration should activate only when its prerequisites are available. Combine class, property, and bean conditions to avoid surprising applications.
@AutoConfiguration
@EnableConfigurationProperties(PaymentProperties.class)
@ConditionalOnClass(PaymentClient.class)
@ConditionalOnProperty(
prefix = "javawhizz.payment",
name = "enabled",
havingValue = "true",
matchIfMissing = true
)
public class PaymentAutoConfiguration {
}
Use @ConditionalOnMissingBean for extension points. This allows a Melbourne retailer to replace the default HTTP client with one that adds tracing, rate limiting, or a local proxy, while the starter still works immediately for simpler services.
Create Defaults That Remain Replaceable
Define the default client inside a nested configuration class. The missing-bean condition prevents the library from competing with application configuration.
@Bean
@ConditionalOnMissingBean
PaymentClient paymentClient(PaymentProperties properties) {
return new PaymentClient(
properties.getBaseUrl(),
properties.getApiKey(),
properties.getTimeout()
);
}
Avoid performing network calls during bean creation. The bean should validate local configuration and prepare its dependencies; API requests belong in service methods. This keeps startup reliable for deployments using Australian payment gateways, private connectivity, or delayed secret injection.
Register Boot 3 Auto-Configuration
Spring Boot 3 discovers auto-configuration through a metadata file rather than the older spring.factories mechanism. Create this file:
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
Add the fully qualified class name:
com.javawhizz.payment.PaymentAutoConfiguration
Use @AutoConfiguration instead of plain @Configuration for modern Boot applications. If ordering matters, use @AutoConfigureAfter or @AutoConfigureBefore sparingly. Ordering should express a real dependency, not conceal an unclear design.
Test Activation And Back-Off
Application context tests should verify both activation and non-activation. ApplicationContextRunner is lightweight and gives precise assertions without starting a web server.
private final ApplicationContextRunner contextRunner =
new ApplicationContextRunner()
.withConfiguration(
AutoConfigurations.of(PaymentAutoConfiguration.class)
);
@Test
void createsClientWhenEnabled() {
contextRunner
.withPropertyValues(
"javawhizz.payment.api-key=test-key"
)
.run(context ->
assertThat(context).hasSingleBean(PaymentClient.class));
}
Useful checks should cover the following:
- The required library is present on the classpath.
- The feature disables when its property is false.
- A user-defined
PaymentClientreplaces the default. - Invalid properties produce a clear startup error.
Add an integration test in a sample application as well. It catches packaging mistakes, missing metadata, and dependency scopes that unit tests can overlook.
Document Operational Behaviour
Explain the property prefix, defaults, required secrets, timeout units, and override mechanism. Include a complete YAML example:
javawhizz:
payment:
enabled: true
base-url: https://sandbox.example.com
timeout: 5s
api-key: ${PAYMENT_API_KEY}
Provide health, metrics, and logging guidance when the client handles transactions. Australian businesses often need audit trails aligned with internal risk controls and privacy obligations, while payment data may also fall under PCI DSS requirements. Never log card details or secret tokens.
Troubleshoot Common Failures
When a bean is missing, inspect the condition evaluation report with --debug. It shows which condition matched or failed and usually reveals a missing dependency, an incorrect property value, or an auto-configuration class that was not registered.
Before releasing a starter, check these packaging and compatibility details:
- Use the same Spring Boot major version as the supported applications.
- Keep optional integrations behind
@ConditionalOnClass. - Verify the generated JAR contains
AutoConfiguration.imports. - Test both default and customised bean scenarios.
- Publish configuration metadata for IDE completion.
A starter should feel invisible when it works: adding one dependency and a few properties should produce a sensible result. Clear conditions, safe defaults, and deliberate override points make that experience dependable for teams building services from Perth to Canberra.