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:

Java
@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:

Java
@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.

Java
@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:

Properties
spring.profiles.active=dev

Profile-specific property files layer on top of the base application.yml, overriding only what differs:

YAML
# application.yml (base — shared by every profile)
spring:
  application:
    name: myapp
logging:
  level:
    root: INFO
YAML
# application-dev.yml (active only when profile "dev" is active)
spring:
  datasource:
    url: jdbc:h2:mem:devdb
logging:
  level:
    root: DEBUG
YAML
# 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:

Java
@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:

YAML
app:
  greeting: "Welcome"
  max-retries: 3
Java
@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 @Bean methods and stereotype annotations for the same kind of class inconsistently — as a rule, use @Component/@Service/@Repository for your own classes, and @Bean for 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 confusing NoSuchBeanDefinitionException at 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 @Value for a large, related group of settings instead of a single @ConfigurationProperties class, leading to scattered, hard-to-audit configuration.