Java

Complete Guide to Spring Boot Custom Auto-Configuration with Conditional Beans and Properties

Learn how to build custom Spring Boot auto-configuration with conditional beans, type-safe properties & validation. Complete guide with examples.

Complete Guide to Spring Boot Custom Auto-Configuration with Conditional Beans and Properties

I’ve been thinking about how Spring Boot simplifies development with its auto-configuration magic. When building multiple applications at my organization, I noticed we kept repeating the same infrastructure code for auditing. Why not create a reusable solution that automatically configures auditing based on our standards? Let’s build a custom Spring Boot auto-configuration together.

Setting up our project requires a multi-module Maven structure. We’ll create separate modules for auto-configuration, starter, and demo app:

<modules>
    <module>audit-spring-boot-autoconfigure</module>
    <module>audit-spring-boot-starter</module>
    <module>audit-demo-app</module>
</modules>

Configuration properties form the foundation of our solution. Using Java records, we define type-safe settings with validation:

@ConfigurationProperties(prefix = "audit")
@Validated
public record AuditProperties(
    @DefaultValue("true") boolean enabled,
    @Valid @NotNull Storage storage
) {
    public record Storage(
        @DefaultValue("database") StorageType type
    ) {}
}

What if we need different storage implementations based on configuration? Conditional beans come to the rescue. Here’s how we conditionally select storage:

@Configuration
@ConditionalOnClass(AuditService.class)
public class AuditAutoConfiguration {

    @Bean
    @ConditionalOnProperty(name = "audit.storage.type", havingValue = "database")
    public AuditStorage databaseStorage() {
        return new DatabaseStorage();
    }
}

The core service implementation handles both synchronous and asynchronous auditing. Notice how we use Spring’s @Async for non-blocking operations:

public class DefaultAuditService implements AuditService {
    @Async("auditTaskExecutor")
    public CompletableFuture<Void> auditAsync(AuditEvent event) {
        // Async implementation
    }
}

How do we ensure everything works as expected? Testing auto-configuration requires verifying conditional behavior:

@SpringBootTest(properties = "audit.enabled=false")
public class AuditDisabledTest {
    @Autowired(required = false)
    private AuditService auditService;
    
    @Test
    public void whenDisabled_thenNoBeansCreated() {
        assertThat(auditService).isNull();
    }
}

Packaging our solution as a starter makes it reusable across projects. The starter module only needs a POM dependency:

<dependencies>
    <dependency>
        <groupId>com.baeldung</groupId>
        <artifactId>audit-spring-boot-autoconfigure</artifactId>
    </dependency>
</dependencies>

Ever wonder how Spring Boot decides which beans to create? The auto-configuration report (--debug flag) reveals the decision process. When troubleshooting, check for:

  1. Missing conditions in @Conditional annotations
  2. Configuration property mismatches
  3. Classpath dependencies not being met

What benefits does this approach bring? Our team now adds auditing to any application with two steps: include the starter dependency and set audit.enabled=true. The auto-configured beans handle the rest, following organizational standards consistently.

Try implementing your own auto-configuration next time you spot repeating patterns. What common services could you package this way? Share your thoughts in the comments - I’d love to hear about your experiences with Spring Boot customization! If you found this useful, please like and share with fellow developers.

// Similar Posts

Keep Reading