Appearance
Spring Boot @Profile Annotation | How Profiling Works in Spring Boot
The Theater Stage Lighting Analogy
Imagine a professional theater company rehearsing a Broadway play. During afternoon rehearsals, the stage crew turns on bright fluorescent work lights. The stage is flooded with harsh white light so carpenters can see floor markings and technicians can test motorized scenery. Nobody expects moody theatrical ambiance during a rehearsal.
At eight o'clock in the evening, when the ticketed audience arrives, the stage manager flicks the lighting preset from "Rehearsal" to "Performance". The fluorescent work lights extinguish. Golden spotlights illuminate the lead actors, blue footlights mimic moonlight, and fog machines trigger. The actors perform the exact same script on the exact same stage, but the surrounding environment is transformed.
In software development, Profiling is that lighting preset switch. Your microservice executes the exact same core business logic, but the surrounding environment changes dramatically across stages. On your local development laptop (dev), you want an embedded H2 in memory database, verbose debug logging, and a mock payment gateway that never charges real credit cards. In production (prod), you require a clustered PostgreSQL database pool, minimal error logging, and live SSL connections to real credit card networks.
This lecture covers Spring Boot profiles, @Profile annotation on beans and configuration classes, profile specific property files, profile expressions, and activating profiles across local, staging, and production environments.
How Profiles Work in Spring Boot
A profile is a named logical group of configuration and beans. You assign beans and property files to specific profiles, and at runtime you specify which profile or profiles are active:
[ Active Profile: "dev" ]
|
+----------------------+----------------------+
| |
[ Dev Configuration ] [ Prod Configuration ]
- Embedded H2 Database - Clustered PostgreSQL DB
- Mock Payment Gateway - Live Stripe Payment Gateway
- DEBUG Log Level - WARN / ERROR Log Level
(ACTIVATED) (IGNORED / NOT LOADED)If a bean is not marked with any profile, it is registered in the default profile, meaning it loads in every environment unless explicitly excluded.
Using @Profile on Beans and Configuration Classes
1. @Profile on Configuration Classes
You can attach @Profile directly to a @Configuration class to activate an entire suite of beans:
java
package com.example.orderservice.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import javax.sql.DataSource;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
@Configuration
@Profile("dev")
public class DevDatabaseConfig {
@Bean
public DataSource dataSource() {
System.out.println("Configuring Embedded H2 Database for DEV profile");
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.build();
}
}java
package com.example.orderservice.config;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import javax.sql.DataSource;
@Configuration
@Profile("prod")
public class ProdDatabaseConfig {
@Bean
public DataSource dataSource() {
System.out.println("Configuring Production PostgreSQL Hikari Pool for PROD profile");
HikariDataSource ds = new HikariDataSource();
ds.setJdbcUrl("jdbc:postgresql://db.prod.company.internal:5432/orderdb");
ds.setUsername("prod_app_user");
ds.setPassword(System.getenv("DB_PASSWORD"));
ds.setMaximumPoolSize(30);
return ds;
}
}2. @Profile on Individual @Bean Methods or Services
You can also place @Profile on specific @Service or @Component classes:
java
package com.example.orderservice.service;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
public interface PaymentGateway {
boolean charge(String accountId, double amount);
}
// Active ONLY in dev and test
@Service
@Profile("dev | test")
public class MockPaymentGateway implements PaymentGateway {
@Override
public boolean charge(String accountId, double amount) {
System.out.println("[MOCK] Automatically approving charge of $" + amount);
return true;
}
}
// Active ONLY in production
@Service
@Profile("prod")
public class StripePaymentGateway implements PaymentGateway {
@Override
public boolean charge(String accountId, double amount) {
System.out.println("[STRIPE LIVE] Charging card for $" + amount);
// Live HTTPS call to Stripe API
return true;
}
}When other services inject PaymentGateway, Spring injects MockPaymentGateway during local dev testing and StripePaymentGateway in production.
Profile Specific Property Files
Spring Boot automatically loads profile specific property files following the naming pattern application-{profile}.properties (or application-{profile}.yml).
Directory Structure:
src/main/resources/
├── application.properties <-- Base shared properties (loaded in ALL profiles)
├── application-dev.properties <-- Loaded ONLY when "dev" profile is active
├── application-qa.properties <-- Loaded ONLY when "qa" profile is active
└── application-prod.properties <-- Loaded ONLY when "prod" profile is activeOrder of Precedence:
Profile specific files override values defined in the base application.properties:
application.properties:propertiesserver.port=8080 app.name=OrderService `app.cache.ttl-seconds=60`application-dev.properties:properties# Overrides server.port and cache ttl for local development server.port=8081 `app.cache.ttl-seconds=5` logging.level.com.example=DEBUG
When running with dev active:
server.portbecomes8081(overridden).app.cache.ttl-secondsbecomes5(overridden).app.nameremainsOrderService(inherited from base file).
Profile Expressions (Boolean Logic)
Spring Boot supports rich boolean expressions inside @Profile:
| Expression Syntax | Meaning |
|---|---|
@Profile("dev") | Active only when dev is active |
@Profile("!prod") | Active in any environment except prod (negation) |
| `@Profile("dev | qa")` |
@Profile("cloud & prod") | Active only when both cloud AND prod are active |
| `@Profile("(dev | qa) & !cloud")` |
How to Activate Profiles
Spring Boot provides multiple ways to set the active profile:
1. In application.properties (Local Development)
properties
spring.profiles.active=dev2. Via Command Line Arguments
bash
java -jar order-service.jar --spring.profiles.active=prod3. Via JVM System Properties
bash
java -Dspring.profiles.active=qa -jar order-service.jar4. Via Operating System Environment Variables (Docker & Kubernetes)
Container orchestrators pass active profiles as standard environment variables:
bash
export SPRING_PROFILES_ACTIVE=prodIn a Kubernetes deployment manifest:
yaml
env:
- name: SPRING_PROFILES_ACTIVE
value: "prod,cloud"Interview Questions & Pitfalls
Q1: What happens if a bean has no @Profile annotation declared?
A bean without a @Profile annotation belongs to the default profile. It is loaded in every environment regardless of which profiles are active, unless another profile specific bean explicitly conflicts with it or overrides it.
Q2: How does property precedence work between application.properties and application-prod.properties?
application.properties defines baseline properties shared by all environments. When the prod profile is activated, application-prod.properties is loaded and its values override any matching keys from application.properties. Unmatched keys remain active from the base file.
Q3: Can multiple profiles be active simultaneously?
Yes. You can activate multiple profiles by separating them with commas: --spring.profiles.active=prod,cloud,us-east. Beans matching any active profile (or matching composite boolean expressions like prod & cloud) will be instantiated.
Q4: What is the risk of using @Profile("!prod")?
Using negation can accidentally allow beans intended strictly for local development (like a mock authentication bypass that auto logs in as root) to run in intermediate testing, performance staging, or disaster recovery environments that are not explicitly named prod. It is safer to use positive whitelisting (@Profile("dev | test")).
Q5: How can you access the active profiles programmatically inside Java code?
Inject Spring's Environment bean (org.springframework.core.env.Environment) and call environment.getActiveProfiles(), which returns a String[] array of all currently active profile names.