CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-springframework-boot--spring-boot-autoconfigure

Spring Boot AutoConfigure provides auto-configuration capabilities that automatically configure Spring applications based on jar dependencies present on the classpath

Pending
Overview
Eval results
Files

custom-auto-configurations.mddocs/guides/

Creating Custom Auto-Configurations

Learn how to create your own auto-configuration classes that integrate seamlessly with Spring Boot.

Basic Auto-Configuration

Step 1: Create Configuration Class

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

@AutoConfiguration
@ConditionalOnClass(MyService.class)
public class MyServiceAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    public MyService myService() {
        return new MyService();
    }
}

Step 2: Register Auto-Configuration

Create META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:

com.example.MyServiceAutoConfiguration

Step 3: Package as JAR

Users can now add your JAR to their classpath and get automatic configuration.

Configuration with Properties

Create Properties Class

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "myservice")
public class MyServiceProperties {
    private boolean enabled = true;
    private int timeout = 30;
    private String endpoint;
    
    // Getters and setters
}

Use in Auto-Configuration

@AutoConfiguration
@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyServiceProperties.class)
public class MyServiceAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(prefix = "myservice", name = "enabled", havingValue = "true", matchIfMissing = true)
    public MyService myService(MyServiceProperties properties) {
        return new MyService(
            properties.getEndpoint(),
            properties.getTimeout()
        );
    }
}

User Configuration

myservice.enabled=true
myservice.timeout=60
myservice.endpoint=https://api.example.com

Ordering Auto-Configurations

Run After Another Configuration

@AutoConfiguration
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MyDatabaseAutoConfiguration {
    
    @Bean
    public DatabaseInitializer initializer(DataSource dataSource) {
        return new DatabaseInitializer(dataSource);
    }
}

Run Before Another Configuration

@AutoConfiguration
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
public class MyWebAutoConfiguration {
    // Runs before web configuration
}

Explicit Order

@AutoConfiguration
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
public class HighPriorityAutoConfiguration {
    // Runs early in the configuration process
}

Conditional Auto-Configuration

Multiple Conditions

@AutoConfiguration
@ConditionalOnClass({DataSource.class, JdbcTemplate.class})
@ConditionalOnProperty(prefix = "app.database", name = "enabled")
@ConditionalOnMissingBean(DataSource.class)
public class DatabaseAutoConfiguration {
    
    @Bean
    public DataSource dataSource() {
        return new HikariDataSource();
    }
}

Custom Conditions

public class OnDatabaseTypeCondition extends SpringBootCondition {
    
    @Override
    public ConditionOutcome getMatchOutcome(
            ConditionContext context,
            AnnotatedTypeMetadata metadata) {
        
        String dbType = context.getEnvironment()
            .getProperty("database.type");
        
        if ("mysql".equals(dbType)) {
            return ConditionOutcome.match("MySQL database configured");
        }
        
        return ConditionOutcome.noMatch("MySQL not configured");
    }
}

@Configuration
@Conditional(OnDatabaseTypeCondition.class)
public class MySQLConfiguration {
    // MySQL-specific configuration
}

Multi-Module Auto-Configuration

Structure

my-autoconfigure/
├── src/main/java/
│   └── com/example/
│       ├── MyServiceAutoConfiguration.java
│       ├── MyServiceProperties.java
│       └── MyService.java
└── src/main/resources/
    └── META-INF/spring/
        └── org.springframework.boot.autoconfigure.AutoConfiguration.imports

Starter Module

Create a separate starter module:

<project>
    <artifactId>my-spring-boot-starter</artifactId>
    
    <dependencies>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>my-autoconfigure</artifactId>
        </dependency>
        <!-- Other required dependencies -->
    </dependencies>
</project>

Testing Auto-Configurations

Basic Test

import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class MyServiceAutoConfigurationTest {
    
    private final ApplicationContextRunner contextRunner = 
        new ApplicationContextRunner()
            .withConfiguration(AutoConfigurations.of(
                MyServiceAutoConfiguration.class
            ));
    
    @Test
    void serviceIsAutoConfigured() {
        contextRunner.run(context -> {
            assertThat(context).hasSingleBean(MyService.class);
        });
    }
    
    @Test
    void serviceNotConfiguredWhenDisabled() {
        contextRunner
            .withPropertyValues("myservice.enabled=false")
            .run(context -> {
                assertThat(context).doesNotHaveBean(MyService.class);
            });
    }
    
    @Test
    void userConfigurationTakesPrecedence() {
        contextRunner
            .withUserConfiguration(UserConfiguration.class)
            .run(context -> {
                assertThat(context).hasSingleBean(MyService.class);
                assertThat(context.getBean(MyService.class))
                    .isInstanceOf(CustomMyService.class);
            });
    }
    
    @Configuration
    static class UserConfiguration {
        @Bean
        public MyService myService() {
            return new CustomMyService();
        }
    }
}

Test with Dependencies

@Test
void autoConfigurationWithDataSource() {
    contextRunner
        .withConfiguration(AutoConfigurations.of(
            DataSourceAutoConfiguration.class,
            MyDatabaseAutoConfiguration.class
        ))
        .withPropertyValues(
            "spring.datasource.url=jdbc:h2:mem:test"
        )
        .run(context -> {
            assertThat(context).hasSingleBean(DataSource.class);
            assertThat(context).hasSingleBean(DatabaseInitializer.class);
        });
}

Best Practices

1. Use Sensible Defaults

@ConfigurationProperties(prefix = "myservice")
public class MyServiceProperties {
    private boolean enabled = true;  // Enabled by default
    private int timeout = 30;         // Reasonable default
    private int retries = 3;          // Safe default
}

2. Allow User Overrides

@Bean
@ConditionalOnMissingBean  // User can provide their own
public MyService myService() {
    return new MyService();
}

3. Fail Fast

@Bean
public MyService myService(MyServiceProperties properties) {
    if (properties.getEndpoint() == null) {
        throw new IllegalStateException(
            "myservice.endpoint must be configured"
        );
    }
    return new MyService(properties.getEndpoint());
}

4. Document Configuration

/**
 * Auto-configuration for MyService.
 * 
 * <p>Activated when {@link MyService} is on the classpath.
 * Can be disabled by setting {@code myservice.enabled=false}.
 * 
 * <p>Configuration properties:
 * <ul>
 *   <li>{@code myservice.enabled} - Enable/disable the service (default: true)</li>
 *   <li>{@code myservice.endpoint} - Service endpoint URL (required)</li>
 *   <li>{@code myservice.timeout} - Request timeout in seconds (default: 30)</li>
 * </ul>
 */
@AutoConfiguration
public class MyServiceAutoConfiguration {
    // ...
}

5. Use Specific Conditions

// Good: Type-safe and fast
@ConditionalOnClass(MyService.class)
@ConditionalOnProperty(prefix = "myservice", name = "enabled")

// Avoid: Slower and error-prone
@ConditionalOnExpression("${myservice.enabled:false}")

Advanced Patterns

Conditional Bean Registration

@AutoConfiguration
public class CacheAutoConfiguration {
    
    @Bean
    @ConditionalOnClass(name = "com.github.benmanes.caffeine.cache.Caffeine")
    @ConditionalOnMissingBean
    public CacheManager caffeineCacheManager() {
        return new CaffeineCacheManager();
    }
    
    @Bean
    @ConditionalOnClass(RedisConnectionFactory.class)
    @ConditionalOnBean(RedisConnectionFactory.class)
    @ConditionalOnMissingBean
    public CacheManager redisCacheManager(RedisConnectionFactory factory) {
        return RedisCacheManager.builder(factory).build();
    }
    
    @Bean
    @ConditionalOnMissingBean
    public CacheManager simpleCacheManager() {
        return new ConcurrentMapCacheManager();
    }
}

Import Additional Configurations

@AutoConfiguration
@Import({
    MyServiceConfiguration.class,
    MyServiceHealthIndicatorConfiguration.class
})
public class MyServiceAutoConfiguration {
    // Main configuration
}

Programmatic Bean Registration

@AutoConfiguration
public class DynamicAutoConfiguration implements ImportBeanDefinitionRegistrar {
    
    @Override
    public void registerBeanDefinitions(
            AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        
        // Dynamically register beans based on environment
        if (shouldRegisterBean()) {
            BeanDefinitionBuilder builder = BeanDefinitionBuilder
                .genericBeanDefinition(MyService.class);
            registry.registerBeanDefinition("myService", 
                builder.getBeanDefinition());
        }
    }
}

Common Pitfalls

1. Circular Dependencies

// Problem: Circular dependency
@Bean
public ServiceA serviceA(ServiceB serviceB) { }

@Bean
public ServiceB serviceB(ServiceA serviceA) { }

// Solution: Use @Lazy
@Bean
public ServiceA serviceA(@Lazy ServiceB serviceB) { }

2. Early Bean Initialization

// Problem: Causes early initialization
@ConditionalOnExpression("@myBean.isReady()")

// Solution: Use properties
@ConditionalOnProperty("mybean.ready")

3. Missing Conditions

// Problem: Always creates bean
@Bean
public MyService myService() { }

// Solution: Add conditions
@Bean
@ConditionalOnClass(MyService.class)
@ConditionalOnMissingBean
public MyService myService() { }

Next Steps

  • Advanced Conditions - Complex conditional logic
  • Testing Guide - Comprehensive testing strategies
  • Real-World Scenarios - Production examples

See Also

  • Core Annotations Reference
  • Conditions Reference
  • Spring Boot Auto-Configuration

Install with Tessl CLI

npx tessl i tessl/maven-org-springframework-boot--spring-boot-autoconfigure@4.0.1

docs

index.md

tile.json