Spring Configuration & Profiles
Java-based @Configuration and @Bean, @ComponentScan, environment profiles, and property injection with @Value.
Java-based configuration
Modern Spring configures beans in plain Java, not XML. A class annotated @Configuration is itself processed by the container, and each @Bean method registers the object it returns as a managed bean, with the method name becoming the bean's default name:
@Configuration
public class AppConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.setConnectTimeout(Duration.ofSeconds(5))
.build();
}
}
@Bean methods are most useful for wiring objects you don't own — third-party classes, or anything that needs constructing with specific arguments — where you can't simply drop a @Component annotation onto the class itself. Any @Bean method's parameters (like RestTemplateBuilder above) are themselves resolved from the container, exactly like constructor injection.
Contrast this with stereotype annotations (@Component, @Service, @Repository), which are the right choice for classes you author yourself — @Bean methods are for everything else.
@ComponentScan
@ComponentScan tells the container which packages to scan for stereotype-annotated classes:
@Configuration
@ComponentScan(basePackages = "com.example.myapp")
public class AppConfig {
}
In a Spring Boot application, the main @SpringBootApplication-annotated class already includes @ComponentScan implicitly, scoped to its own package and all sub-packages — which is exactly why Boot apps are conventionally structured with the main class at the root package, so scanning naturally covers everything underneath. You rarely write @ComponentScan explicitly in a Boot app; it matters more when configuring plain Spring or scanning outside the default package tree.
Profiles: environment-specific beans and configuration
A profile is a named, conditionally-active configuration group — the standard way to have different beans or settings for dev, test, and prod without branching logic in your code.
@Configuration
public class DataSourceConfig {
@Bean
@Profile("dev")
public DataSource devDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
@Bean
@Profile("prod")
public DataSource prodDataSource() {
return new HikariDataSource(prodHikariConfig());
}
}
Only the bean matching the currently active profile is registered; the other one is skipped entirely. The active profile is set via a property or environment variable:
spring.profiles.active=dev
Profile-specific property files layer on top of the base application.yml, overriding only what differs:
# application.yml (base — shared by every profile)
spring:
application:
name: myapp
logging:
level:
root: INFO
# application-dev.yml (active only when profile "dev" is active)
spring:
datasource:
url: jdbc:h2:mem:devdb
logging:
level:
root: DEBUG
# application-prod.yml
spring:
datasource:
url: jdbc:postgresql://prod-db:5432/myapp
@Profile also works directly on a @Component/@Service class, not just @Bean methods, when you want an entire implementation swapped per environment:
@Service
@Profile("!prod") // active for every profile EXCEPT prod
public class FakeEmailService implements EmailService {
public void send(String to, String subject, String body) {
System.out.println("Pretend-sending email to " + to);
}
}
Property injection with @Value
@Value pulls a single configuration property (from application.yml/.properties, environment variables, or system properties) directly into a field or constructor parameter:
app:
greeting: "Welcome"
max-retries: 3
@Service
public class GreetingService {
private final String greeting;
private final int maxRetries;
public GreetingService(
@Value("${app.greeting}") String greeting,
@Value("${app.max-retries:5}") int maxRetries // ":5" is a default if the property is absent
) {
this.greeting = greeting;
this.maxRetries = maxRetries;
}
}
For more than a couple of related properties, a type-safe @ConfigurationProperties class is usually a better fit than a pile of individual @Value fields — it groups related settings, validates them, and gives you IDE auto-completion — but @Value remains the simplest tool for a single, standalone property.
Common mistakes
- Mixing
@Beanmethods and stereotype annotations for the same kind of class inconsistently — as a rule, use@Component/@Service/@Repositoryfor your own classes, and@Beanfor third-party or externally-constructed objects. - Forgetting that only one profile's
@Bean/@Profile-annotated class is active at a time — if no profile is active and a bean requires one, it simply won't be registered, which surfaces as a confusingNoSuchBeanDefinitionExceptionat startup. - Hardcoding environment-specific values (database URLs, API keys) instead of externalizing them through profiles and
application-{profile}.yml— makes the same build promotable across environments without code changes. - Using
@Valuefor a large, related group of settings instead of a single@ConfigurationPropertiesclass, leading to scattered, hard-to-audit configuration.