What Is a Profile?
A Spring Boot Profile is a switch that represents “which environment the application is currently running in” (development, staging, production, and so on).
Depending on this switch, you can enable or disable Beans and change configuration values (database connection targets, log levels, external API URLs, etc.).
For example, you can safely perform switches like the following:
- H2 in development, PostgreSQL in production
- Verbose logging in development, mostly INFO in production
- A mock external API in development, the real API in production
The major value of Profiles is that they reduce accidents like “manually editing the configuration and then deploying.”
What Does a Profile Switch?
There are two main things that a Profile switches:
- Values in configuration files (application.yml / properties)
- Activation of Spring Bean definitions (@Bean, @Component, etc.)
The Basics of Splitting Configuration Files per Profile
The most common approach is splitting configuration into application-<profile>.yml (or .properties) files.
For example, you can split them as follows:
application.yml(shared)application-dev.yml(for development)application-prod.yml(for production)application-test.yml(for tests)
application.yml (shared)
spring:
application:
name: demo
logging:
level:
root: INFO
application-dev.yml (development)
spring:
datasource:
url: jdbc:h2:mem:testdb
jpa:
hibernate:
ddl-auto: create-drop
logging:
level:
root: DEBUG
application-prod.yml (production)
spring:
datasource:
url: jdbc:postgresql://db.prod.example.com:5432/app
jpa:
hibernate:
ddl-auto: validate
logging:
level:
root: INFO
application-test.yml (tests)
spring:
datasource:
url: jdbc:h2:mem:testdb
jpa:
hibernate:
ddl-auto: create-drop
logging:
level:
root: WARN
The key point is to “put shared settings in application.yml and write only the environment-specific differences on the profile side.”
This makes the differences easier to see and reduces duplicated configuration.
How to Activate a Profile
There are several ways to activate a Profile (i.e., choose which one to use). Here they are, in order of how commonly they are used.
Specify It as a Startup Argument
This is convenient for running locally or for temporary switches.
java -jar app.jar --spring.profiles.active=dev
Specify It as a JVM System Property (-D)
You can also pass it as a JVM system property, like -Dspring.profiles.active=.... The key is to write it right after the java command and before the jar file name. If you write it after the jar file name, it is treated as an application argument and will not set the Profile.
java -Dspring.profiles.active=prod -jar app.jar
Since this specifies the same key as the startup argument (--spring.profiles.active), the effect is almost identical. If both are specified, the startup argument takes precedence.
When writing into “VM options” in an IntelliJ IDEA run configuration, or in environments that have a mechanism for passing JVM options in bulk such as JAVA_TOOL_OPTIONS, this -D form is easier to work with.
Specify It as an Environment Variable
This is commonly used in container operations such as Docker and Kubernetes.
export SPRING_PROFILES_ACTIVE=prod
For concrete Manifest examples of passing environment variables and Secrets from a Kubernetes Deployment, see How to Deploy a Spring Boot App to Kubernetes.
Specify It in application.yml (Not Recommended in Some Situations)
spring:
profiles:
active: dev
Be careful with this, because it tends to result in a state where the app “always runs as dev.”
If CI/CD or production deployments are involved, it is safer to control this from environment variables or the deployment configuration.
Always Enabling the test Profile Only When Running Tests with Gradle
If you want to “use the test Profile every time tests are run with ./gradlew test,” the simplest way is to pass it as a system property in Gradle’s test task.
Groovy DSL (build.gradle)
tasks.named('test') {
useJUnitPlatform()
systemProperty 'spring.profiles.active', 'test'
}
With this, the test Profile is always active as long as tests are run through Gradle.
If you prefer to pass it as an environment variable, you can also write it like this:
tasks.named('test') {
useJUnitPlatform()
environment 'SPRING_PROFILES_ACTIVE', 'test'
}
Kotlin DSL (build.gradle.kts)
tasks.test {
useJUnitPlatform()
systemProperty("spring.profiles.active", "test")
}
Things to Note
- This setting only affects “Gradle’s
testtask.” It has no effect onbootRunand the like. - If your IDE “runs JUnit directly (a run configuration that does not use Gradle),” this setting may not be applied. In that case, consider switching the IDE to “run tests with Gradle,” or adding
@ActiveProfiles("test")to your test classes.
Specifying the test Profile in a Test Class with @ActiveProfiles
If you want the test code itself to declare “this test runs with the test Profile” rather than relying on the Gradle configuration, use @ActiveProfiles. Combining it with @SpringBootTest is the standard approach.
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
@SpringBootTest
@ActiveProfiles("test")
class UserServiceTest {
}
With this, application-test.yml is loaded whenever this test class runs. The nice part is that you get the same result whether you run it directly from the IDE or through Gradle.
@ActiveProfiles takes precedence over spring.profiles.active. Even if Gradle’s test task passes a different Profile, remember that the annotation wins.
If writing it on every test class is tedious, prepare a single abstract class annotated with @SpringBootTest and @ActiveProfiles("test"), and simply have each test class extend it.
Also, when you only want to override a few values in a specific test, @TestPropertySource or @SpringBootTest(properties = "...") is often sufficient rather than adding another Profile. Using Profiles for “switching the whole environment” and property overrides for “small tweaks for this test only” keeps your configuration files from getting cluttered.
Switching Beans with Profiles
Not only configuration values but also Beans themselves can be switched per Profile.
Switching per Class with @Profile
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
@Profile("dev")
@Component
public class DevOnlyInitializer {
}
This Bean is registered only when the dev Profile is active.
For things like “loading initial data at startup only in the development environment,” the standard approach is to implement CommandLineRunner in a class of this form. For how to write startup processing, see How to Run Startup Logic with CommandLineRunner and ApplicationRunner in Spring Boot.
It Also Works with @Configuration + @Bean
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Configuration
public class ClientConfig {
@Profile("dev")
@Bean
public ApiClient mockClient() {
return new ApiClient("http://localhost:8081");
}
@Profile("prod")
@Bean
public ApiClient realClient() {
return new ApiClient("https://api.example.com");
}
}
This lets you build configurations such as swapping the external API client per environment.
Specifying More Than One Profile
Multiple Profiles can be active at the same time.
--spring.profiles.active=dev,feature-x
In this case, both dev and feature-x are active.
Designing your Profiles so that “environment” and “feature flags” are separated makes switching smoother.
Activating Profiles Together with Groups
As the number of Profiles grows, you will run into requests like “when dev is selected, I want the dev and local ones to be turned on together.”
In that case, Profile Groups are handy.
spring:
profiles:
group:
dev:
- dev
- local
With this, setting spring.profiles.active=dev activates both dev and local together.
The Multi-Document Format for Writing Everything in One File
Since Spring Boot 2.4, instead of splitting into multiple files, you can also separate sections inside a single application.yml with --- and use spring.config.activate.on-profile to declare “which Profile this block is active for.”
spring:
application:
name: demo
---
spring:
config:
activate:
on-profile: dev
datasource:
url: jdbc:h2:mem:testdb
---
spring:
config:
activate:
on-profile: prod
datasource:
url: jdbc:postgresql://db.prod.example.com:5432/app
The first block is always loaded, and blocks with on-profile are applied only when the corresponding Profile is active. A recommended approach is to keep everything in one file while the configuration is small, since it is easy to get an overview, and then split it out into application-<profile>.yml files as it grows.
The spring.profiles: dev notation you may see in older articles was deprecated in Spring Boot 2.4 and causes a startup error in the 3.x series. For new code, use spring.config.activate.on-profile.
Also Know About include and default
spring.profiles.include is a setting for “always activating these in addition to the Profiles that were activated.” When you want to add a Profile such as common that is used in every environment, write something like spring.profiles.include: common.
spring.profiles.default is “the Profile used when no Profile is specified.” Spring Boot’s initial value is a Profile named default, and if application-default.yml exists, it is loaded.
One caveat: spring.profiles.include cannot be written inside a block with on-profile or inside application-<profile>.yml (it causes a startup error). spring.profiles.group is also a setting that determines the Profile structure itself, so it is safest to put it in the shared section (the first block) or in the main application.yml.
Common Pitfalls and Countermeasures
Starting Production with the dev Configuration
The cause is usually one of the following:
spring.profiles.active=devis written inapplication.yml- The environment variable is not set in the deployment environment
- The startup script is outdated
As a countermeasure, we strongly recommend “always explicitly setting SPRING_PROFILES_ACTIVE=prod in the deployment configuration for production.”
Losing Track of Configuration Precedence
Having the same key in multiple places causes confusion. The basic mental model is this:
- “Further outside” (environment variables and startup arguments) is stronger
- “More specific” (the profile side) is often stronger
If you get stuck, the startup log shows which Profiles are active, so checking there first is the fastest way to resolve it.
application-xxx.yml Is Not Being Loaded
Typos in the Profile name are a common cause.
- You prepared
application-prod.ymlbut started with--spring.profiles.active=production
→ The file name and the Profile name must match
Recommended Setup for Development and Operations
If in doubt, this structure is easy to work with.
application.yml: shared configurationapplication-dev.yml: differences for the development environmentapplication-prod.yml: differences for the production environmentapplication-test.yml: differences for the test environment- Always explicitly set
SPRING_PROFILES_ACTIVEat startup (especially in production)
For “values that should exist only in production (passwords, API keys, etc.),” it is safer to pass them via environment variables or secret management (such as Kubernetes Secrets) rather than hard-coding them in configuration files.
If there are values you absolutely must keep on the configuration file side, another option is to commit only encrypted strings. The steps are explained in How to Encrypt Sensitive Values in Configuration Files with Jasypt in Spring Boot.
Sorting Out Precedence to Avoid Confusion
When the same key is written in multiple places, it is easy to lose track of which value was adopted.
In practice, remembering the following order of precedence (strongest first) speeds up troubleshooting.
- Startup arguments (
--spring.profiles.active=...) - Environment variables (
SPRING_PROFILES_ACTIVE) application-<profile>.ymlapplication.yml
Adopting the operational policy of “explicitly set production via environment variables, and keep configuration files focused on defining differences” reduces accidents.
Operational Rules for Team Development
When Profile management breaks down, environment-specific bugs become frequent. We recommend the following rules:
- Do not hard-code
spring.profiles.activeinapplication.yml - Record “which Profile was used for verification” in pull requests
- Whenever a new configuration key is added, always check whether it differs across
dev/prod/test - Do not keep secrets in the repository; move them to the environment’s Secret management
Profiles are convenient, but they only work safely when paired with operational rules.
Summary
Using Spring Boot Profiles lets you safely switch configuration and Beans per environment.
- Separate configuration values with
application-<profile>.yml - Activate them with
--spring.profiles.activeorSPRING_PROFILES_ACTIVE - Swap Beans per environment with
@Profile - Passing
spring.profiles.active=testin Gradle’stesttask pins the Profile only when running tests - Prevent accidents in production by “always explicitly specifying prod”
Take advantage of Profiles to separate your environments safely!