Spring Boot Profiles
An application runs in several places: on your laptop, on a test server and in production. Each place needs different settings, such as a different database, different log levels or a real payment service instead of a fake one. Spring Boot profiles let one codebase carry all these settings and pick the right ones for each environment, without changing a single line of code.
This tutorial covers profile-specific files, every way to activate a profile, @Profile on beans, profile groups, YAML, logging, testing and keeping secrets safe.
1. What Are Spring Boot Profiles?
Spring Boot profiles provide a mechanism for defining environment-specific configurations for your Spring Boot application. You can activate a specific profile, such as development, testing, or production, when running your application. Spring Boot then loads the configuration associated with the active profile.
Common profile names are:
| Profile | Environment | Typical purpose |
|---|---|---|
dev or local | Your own machine | Fast feedback, detailed logs, local database |
test | Automated tests | In-memory database, fake external services |
staging | Pre-production server | Production-like setup for final checks |
prod | Production | Real database, strict security, quiet logs |
The names are your choice; Spring Boot does not reserve them, apart from the built-in default profile. What matters is that the same code runs everywhere, and only the active profile changes.
2. Why Do We Need Profiles?
| Setting | Development | Test | Production |
|---|---|---|---|
| Database | Local PostgreSQL | H2 in memory | Managed cloud database |
| Log level | DEBUG | WARN | INFO |
| Payment service | Fake that always approves | Mock | Real payment provider |
| Emails | Printed to the log | Not sent | Sent through a mail server |
| Error details | Shown in responses | Shown | Hidden |
Without profiles, you would have to edit configuration files, or worse, code, before each deployment. That is slow and error-prone: one forgotten change and production connects to the wrong database. Profiles make each environment’s setup explicit and repeatable.
3. How Spring Boot Loads Profile Configuration
At startup, Spring Boot first reads the shared configuration, then layers the configuration of each active profile on top:
application.properties <- always loaded: settings shared by every environment
+
application-{profile}.properties <- loaded for each active profile; wins on conflicts
+
environment variables / command-line arguments <- highest priority of these
The rule to remember: profile-specific values override shared values. Put everything common in application.properties, and only the differences in the profile files.
4. Profile-Specific Properties Files
Create one file per profile next to application.properties, in src/main/resources:
application.properties
application-dev.properties
application-test.properties
application-prod.properties
# Shared by all environments
spring.application.name=shop-service
server.servlet.context-path=/shop
spring.jpa.open-in-view=false
server.port=8081
spring.datasource.url=jdbc:postgresql://localhost:5432/shop_dev
spring.datasource.username=dev
spring.datasource.password=dev
logging.level.com.javabytechie=DEBUG
server.port=8080
spring.datasource.url=${DB_URL}
spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}
logging.level.root=INFO
Notice the production file contains no real passwords: ${DB_PASSWORD} is read from an environment variable at startup. Section 14 explains why this matters.
5. Activating a Profile
There are several ways to choose the active profile. Pick the one that fits where the application runs:
| Where | How | Example |
|---|---|---|
| Command line (program argument) | --spring.profiles.active after the JAR name | java -jar shop.jar --spring.profiles.active=prod |
| JVM system property | -D before -jar | java -Dspring.profiles.active=prod -jar shop.jar |
| Environment variable | SPRING_PROFILES_ACTIVE | export SPRING_PROFILES_ACTIVE=prod |
| Maven during development | Plugin property | ./mvnw spring-boot:run -Dspring-boot.run.profiles=dev |
| Gradle during development | Program argument | ./gradlew bootRun --args='--spring.profiles.active=dev' |
| IDE run configuration | Active profiles field, or a VM option | -Dspring.profiles.active=dev |
application.properties | A fallback value | spring.profiles.active=dev |
The command line and environment variables take priority over the properties file. That makes the environment variable ideal for servers and containers: the same JAR is deployed everywhere, and only the variable differs.
# docker run
# docker run -e SPRING_PROFILES_ACTIVE=prod shop-service:1.0
# Kubernetes Deployment (excerpt)
env:
- name: SPRING_PROFILES_ACTIVE
value: "prod"
You can also add profiles in code, which is handy for a dedicated local launcher class:
public class LocalShopApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(ShopApplication.class);
app.setAdditionalProfiles("dev");
app.run(args);
}
}
6. Checking Which Profiles Are Active
Spring Boot logs the active profiles near the top of every startup:
The following 1 profile is active: "dev"
If no profile is set, you see this instead, which is a useful warning in production:
No active profile set, falling back to 1 default profile: "default"
To react to the active profile in code, ask the Environment. Use this sparingly; @Profile is usually cleaner:
@Component
public class StartupBanner {
private static final Logger log = LoggerFactory.getLogger(StartupBanner.class);
public StartupBanner(Environment environment) {
log.info("Active profiles: {}", Arrays.toString(environment.getActiveProfiles()));
if (environment.acceptsProfiles(Profiles.of("prod"))) {
log.info("Running with production settings");
}
}
}
In a running application, Spring Boot Actuator’s /actuator/env endpoint also lists the active profiles, when that endpoint is exposed.
7. @Profile on Beans
Properties change values. @Profile goes further: it decides which beans exist. A classic use is a fake implementation for development and a real one for production:
public interface PaymentGateway {
PaymentResult charge(String orderId, BigDecimal amount);
}
@Service
@Profile({"dev", "test"})
public class FakePaymentGateway implements PaymentGateway {
private static final Logger log = LoggerFactory.getLogger(FakePaymentGateway.class);
@Override
public PaymentResult charge(String orderId, BigDecimal amount) {
log.info("FAKE payment of {} for order {}", amount, orderId);
return PaymentResult.approved("fake-" + orderId); // no real money moves
}
}
@Service
@Profile("prod")
public class StripePaymentGateway implements PaymentGateway {
@Override
public PaymentResult charge(String orderId, BigDecimal amount) {
// call the real payment provider
}
}
@Service
public class CheckoutService {
private final PaymentGateway paymentGateway; // the fake or the real one, depending on the profile
public CheckoutService(PaymentGateway paymentGateway) {
this.paymentGateway = paymentGateway;
}
}
CheckoutService never knows which implementation it gets. Listing several profiles, as in @Profile({"dev", "test"}), means the bean is created if any of them is active.
8. @Profile on Configuration Classes and Expressions
Put @Profile on a configuration class to switch a whole group of beans together, or on a single @Bean method:
@Configuration
@Profile("test")
public class TestDataConfig {
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("schema.sql")
.addScript("test-data.sql")
.build();
}
}
@Configuration
public class NotificationConfig {
@Bean
@Profile("prod")
public EmailSender smtpEmailSender(JavaMailSender mailSender) {
return new SmtpEmailSender(mailSender);
}
@Bean
@Profile("!prod") // every profile except prod
public EmailSender loggingEmailSender() {
return new LoggingEmailSender(); // prints emails to the log instead of sending
}
}
8.1 Profile expressions
| Expression | Bean is created when... |
|---|---|
@Profile("prod") | prod is active |
@Profile("!prod") | prod is not active |
@Profile({"dev", "test"}) | dev or test is active |
@Profile("dev | test") | Same as above, written as an expression |
@Profile("prod & eu") | prod and eu are both active |
@Profile("prod & !eu") | prod is active and eu is not |
!prod is a safe pattern for anything that must never run in production, such as sample data loaders.
9. The Default Profile
When no profile is activated, Spring uses a profile named default. Beans marked @Profile("default") are created only in that case:
@Component
@Profile("default")
public class InMemoryCatalog implements Catalog {
// used only when nobody chose a profile, e.g. a quick local run
}
You can also rename which profile is used when none is chosen:
spring.profiles.default=local
Now local is used automatically on a developer’s machine, while production must set prod explicitly. As soon as any profile is activated, the default profile no longer applies.
10. Multiple Profiles and Profile Groups
Several profiles can be active together. Separate them with commas:
java -jar shop.jar --spring.profiles.active=prod,eu
All matching files are loaded. When two profile files set the same property, the profile listed last wins; here, a value in application-eu.properties overrides one in application-prod.properties.
10.1 Profile groups
When an environment always needs the same set of profiles, define a group so you activate one name instead of a list:
spring.profiles.group.production=prod,postgres,kafka
spring.profiles.group.local=dev,h2,mock-payments
java -jar shop.jar --spring.profiles.active=production
# activates: production, prod, postgres, kafka
Groups keep deployment settings short and stop people from forgetting one profile of the set.
11. Profiles in YAML
With YAML, you can keep all environments in one application.yml file, separated by ---. Indentation matters in YAML, so use two spaces per level:
# Shared settings
spring:
application:
name: shop-service
server:
servlet:
context-path: /shop
---
spring:
config:
activate:
on-profile: dev
server:
port: 8081
logging:
level:
com.javabytechie: DEBUG
---
spring:
config:
activate:
on-profile: prod
server:
port: 8080
logging:
level:
root: INFO
Each section after --- applies only when its on-profile is active. Separate files such as application-dev.yml work just as well, and are easier to scan when the configuration grows.
12. Profile-Based Logging
For simple cases, set log levels in each profile’s properties file. For more control, such as writing to a file only in production, use logback-spring.xml with <springProfile> sections:
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
<springProfile name="dev | test">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
<logger name="com.javabytechie" level="DEBUG"/>
</springProfile>
<springProfile name="prod">
<property name="LOG_FILE" value="${LOG_FILE:-logs/shop-service.log}"/>
<include resource="org/springframework/boot/logging/logback/file-appender.xml"/>
<root level="INFO">
<appender-ref ref="FILE"/>
</root>
</springProfile>
</configuration>
The file must be named logback-spring.xml, not logback.xml, for the <springProfile> tag to work, because Spring Boot has to process it.
13. Profiles in Tests
Activate a profile for a test class with @ActiveProfiles:
@SpringBootTest
@ActiveProfiles("test")
class CheckoutServiceTest {
@Autowired
private CheckoutService checkoutService;
@Autowired
private PaymentGateway paymentGateway;
@Test
void usesTheFakePaymentGatewayInTests() {
assertThat(paymentGateway).isInstanceOf(FakePaymentGateway.class);
}
}
This loads application.properties plus application-test.properties, and creates beans marked for the test profile. Put shared test settings in src/test/resources/application-test.properties.
Keep the set of profiles used across your test classes small and consistent. Every different combination forces Spring to start a new application context, which slows the test suite down.
14. Keeping Secrets Out of Profile Files
Profile files are part of your code repository, so anyone with access to the repository can read them. Never put production passwords or API keys in them. Use placeholders instead, and supply the real values from outside:
spring.datasource.password=${DB_PASSWORD}
payment.api-key=${PAYMENT_API_KEY}
# A default after the colon is used if the variable is missing (fine for non-secrets only)
payment.timeout=${PAYMENT_TIMEOUT:5s}
| Source of secrets | Good for |
|---|---|
| Environment variables | Simple servers and containers |
| Kubernetes Secrets | Applications on Kubernetes |
| A secrets manager (cloud or Vault) | Larger systems with rotation and auditing |
For a secret with no default, such as ${DB_PASSWORD}, a missing value stops the application at startup, which is far better than starting with an empty password.
15. Use Cases and Best Practices
| Use case | How profiles help |
|---|---|
| Different databases per environment | Datasource settings in each profile file |
| Fake external services in development | @Profile chooses fake or real beans |
| Log levels and log destinations | Profile files or logback-spring.xml |
| Sample data only outside production | A data loader marked @Profile("!prod") |
| Regional differences | Combine prod with eu or us |
- Keep shared settings in
application.properties, and only differences in profile files. - Activate profiles from outside the JAR, for example with
SPRING_PROFILES_ACTIVE, so one build runs everywhere. - Keep the number of profiles small, and use groups for common combinations.
- Use
@Profilefor infrastructure choices, such as fake vs real services, not for business rules. - Never store secrets in profile files; use placeholders.
- Protect production with
@Profile("!prod")on anything that must never run there. - Check the startup log to confirm the expected profiles are active after each deployment.
16. Common Mistakes to Avoid
| Mistake | What goes wrong | Fix |
|---|---|---|
Typo in a file name, such as application_dev.properties | The file is silently ignored | Use a hyphen: application-dev.properties |
Committing spring.profiles.active=dev in application.properties | Production may start with dev settings if nothing overrides it | Set the profile outside the JAR; use spring.profiles.default for local runs |
Setting spring.profiles.active inside a profile-specific file | Startup fails with an invalid property error | Use profile groups instead |
Misspelled profile name, such as prd | No error, but no profile settings are applied | Check the “profile is active” line in the log |
Using the old spring.profiles: key in YAML documents | Not supported in current Spring Boot versions | Use spring.config.activate.on-profile |
| Real passwords in profile files | Secrets end up in version control | Use ${...} placeholders |
17. Summary
Spring Boot profiles let one application adapt to each environment. Put shared settings in application.properties and differences in application-{profile}.properties or YAML sections, activate profiles from outside with SPRING_PROFILES_ACTIVE or --spring.profiles.active, and use @Profile to choose between fake and real beans. Profile groups, expressions such as !prod, profile-aware logging and @ActiveProfiles in tests cover the rest. Keep secrets out of the files, and always check which profiles are active.
Think of profiles like the driving modes of a car: the same engine and the same driver, but Eco, Comfort or Sport change how it behaves, at the press of a button.
Frequently Asked Questions (FAQ)
Q1. What is the difference between @Profile and @ConditionalOnProperty?
@Profile | @ConditionalOnProperty | |
|---|---|---|
| Decides based on | Which profiles are active | The value of one property |
| Granularity | A whole environment | A single feature |
| Typical use | Fake vs real services per environment | Switching one feature on or off |
@Bean
@ConditionalOnProperty(name = "shop.recommendations.enabled", havingValue = "true")
public RecommendationService recommendationService() { ... }
Use @Profile when the choice depends on where the app runs, and @ConditionalOnProperty when it depends on whether a feature is wanted, which may differ even between two production deployments.
Q2. Are profile names case-sensitive?
Yes. prod, Prod and PROD are three different profiles. Activating Prod does not load application-prod.properties, and beans marked @Profile("prod") are not created.
Stick to one style for every profile name, ideally lowercase letters with hyphens, such as prod, local-docker or eu-west.
Q3. How can I make the application refuse to start without a profile?
Accidentally starting production with no profile is a real risk. Two simple safeguards:
- Required placeholders: production settings such as
${DB_PASSWORD}without a default stop startup when missing. - An explicit check at startup:
@Component
public class ProfileGuard {
public ProfileGuard(Environment environment) {
if (environment.getActiveProfiles().length == 0) {
throw new IllegalStateException(
"No active profile set. Use SPRING_PROFILES_ACTIVE=dev|test|prod");
}
}
}
Combined with a clear message, this turns a silent misconfiguration into an obvious, immediate error.
Q4. Can profile configuration files live outside the JAR?
Yes. Spring Boot also looks for configuration files next to the JAR, and those override the ones packaged inside it:
/opt/shop/
├── shop-service.jar
└── config/
├── application.properties
└── application-prod.properties
To use another folder, point to it at startup:
java -jar shop-service.jar --spring.config.additional-location=file:/etc/shop/
This lets operations teams adjust settings for a server without rebuilding the application.
Q5. If application.yml and application.properties both exist, which wins?
Both are loaded. If they set the same property in the same location, the .properties file wins over the .yml file. The same applies to profile files, such as application-dev.properties over application-dev.yml.
Mixing both formats in one project is confusing, so choose one format and use it everywhere.
Q6. What is the difference between Maven profiles and Spring profiles?
| Maven profiles | Spring profiles | |
|---|---|---|
| When they apply | At build time | At run time |
| Change | Dependencies, plugins, which files are packaged | Configuration values and which beans exist |
| Activated with | mvn -Pprod package | --spring.profiles.active=prod |
Prefer Spring profiles. They let one JAR run in every environment. Building a separate JAR per environment with Maven profiles means production may run a binary that was never tested.
Q7. Can a profile switch off an auto-configuration?
Yes. Add the exclusion to that profile’s file only:
# application-demo.properties: run the demo without any database
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
Other profiles keep the auto-configuration. This is handy for lightweight demo or documentation modes that do not need every external system.
Q8. Can different test methods use different profiles?
Not within one test class. @ActiveProfiles applies to the whole class, because the Spring context is created once per configuration. Use separate test classes instead:
@SpringBootTest
@ActiveProfiles("test")
class CheckoutWithFakePaymentsTest { ... }
@SpringBootTest
@ActiveProfiles({"test", "real-payments-sandbox"})
class CheckoutWithSandboxPaymentsTest { ... }
Remember that each unique combination of profiles creates its own context, so keep the number of combinations small.
Q9. How do profiles work with @ConfigurationProperties?
Automatically. A @ConfigurationProperties class reads its values from the combined configuration, so each environment simply gets its own values:
# application-dev.properties
shop.shipping.free-above=0
# application-prod.properties
shop.shipping.free-above=999
@ConfigurationProperties(prefix = "shop.shipping")
public record ShippingProperties(BigDecimal freeAbove) { }
The class stays the same; only the values change with the active profile.
Q10. How can I see which file a property value came from?
Expose Actuator’s env endpoint during development, then look up the property:
curl http://localhost:8080/actuator/env/server.port
The response lists every source that defines the property, such as application-dev.properties, application.properties or an environment variable, and which value is actually used. This quickly explains “why is my setting ignored?” problems.
Keep this endpoint closed or secured in production, because it reveals configuration details.
Q11. Should I use profiles as feature flags?
Usually not. Profiles describe environments. If you create a profile for every feature, such as new-checkout or beta-search, the number of combinations grows quickly and becomes hard to reason about.
For features, use a simple property:
shop.features.new-checkout=true
Read it with @ConditionalOnProperty or @Value. For features that change at runtime or per user, a dedicated feature-flag tool is a better fit.
Q12. How many profiles should a project have?
As few as possible. Most applications are well served by three or four:
localordevfor developer machines.testfor automated tests.prodfor production, plusstagingif you have it.
When you need finer pieces, such as postgres or kafka, combine them into a few groups so people still activate just one name per environment.
Q13. What is spring.profiles.include, and should I use it?
spring.profiles.include adds extra profiles whenever the configuration is loaded, on top of the active ones. In current Spring Boot versions it may only be set in non-profile-specific files, such as application.properties.
For most needs, profile groups are clearer: they say “when production is active, also activate these”, all in one place. Use include only for profiles that must be active in every environment.
Q14. Do profiles affect application performance?
No. Profiles are resolved once, at startup, to decide which files to read and which beans to create. After that, the running application is the same as one configured without profiles.
The settings inside a profile can of course affect performance, for example a DEBUG log level in production would slow things down, which is one more reason to check the active profile after every deployment.
Q15. Does @ActiveProfiles work with slice tests like @WebMvcTest?
Yes. @ActiveProfiles works with every Spring test, including slices:
@WebMvcTest(ProductController.class)
@ActiveProfiles("test")
class ProductControllerTest {
// application-test.properties is loaded, and @Profile("test") beans
// are created if they belong to the web slice
}
Remember that a slice only loads part of the application, so a @Profile("test") service is still not created in a @WebMvcTest. Provide it with @MockitoBean instead.
Q16. How can each developer keep personal settings without committing them?
Use a personal profile file that is ignored by version control:
# .gitignore
src/main/resources/application-local.properties
# application-local.properties (on your machine only)
spring.datasource.url=jdbc:postgresql://localhost:5433/shop
spring.datasource.password=my-local-password
Each developer activates dev,local. Shared development settings live in the committed application-dev.properties, while personal overrides, such as a different port or password, stay on their own machine. Commit an application-local.properties.example file to show what can be set.
Q17. Can Spring Boot apply settings automatically when running on Kubernetes?
Yes. Besides profiles, a configuration document can activate on the detected cloud platform:
---
spring:
config:
activate:
on-cloud-platform: kubernetes
management:
endpoint:
health:
probes:
enabled: true
Spring Boot detects Kubernetes from environment variables that Kubernetes sets in every container. The same approach works for other platforms, such as Cloud Foundry, without anyone choosing a profile.
Q18. Why does SPRING_PROFILES_ACTIVE work as an environment variable?
Many systems do not allow dots in environment variable names, so Spring Boot uses relaxed binding: it converts SPRING_PROFILES_ACTIVE into spring.profiles.active automatically. The rule is simple: uppercase, and replace dots and hyphens with underscores.
| Property | Environment variable |
|---|---|
spring.profiles.active | SPRING_PROFILES_ACTIVE |
server.port | SERVER_PORT |
spring.datasource.url | SPRING_DATASOURCE_URL |
This lets you override any setting, not just the profile, from a container or server environment.
Q19. How do profiles work with a central configuration server?
Central configuration servers, such as Spring Cloud Config, use the same profile names. When an application named shop-service starts with the prod profile, it fetches files like:
shop-service.yml <- shared settings for this application
shop-service-prod.yml <- production settings for this application
So everything on this page still applies; the files simply live in a central repository instead of inside each JAR, which makes it easier to manage many services together.
Q20. Are profiles a Spring Boot feature or a Spring Framework feature?
Both, in layers. The Spring Framework provides the core: @Profile, the Environment API and the spring.profiles.active property.
Spring Boot adds the conveniences on top: loading application-{profile}.properties and YAML files automatically, profile groups, spring.config.activate.on-profile, <springProfile> in logging configuration, and clear startup log messages. See Spring Boot vs Spring Framework for more on how the two fit together.