00:00

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:

ProfileEnvironmentTypical purpose
dev or localYour own machineFast feedback, detailed logs, local database
testAutomated testsIn-memory database, fake external services
stagingPre-production serverProduction-like setup for final checks
prodProductionReal 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?

SettingDevelopmentTestProduction
DatabaseLocal PostgreSQLH2 in memoryManaged cloud database
Log levelDEBUGWARNINFO
Payment serviceFake that always approvesMockReal payment provider
EmailsPrinted to the logNot sentSent through a mail server
Error detailsShown in responsesShownHidden

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:

Loading order
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:

src/main/resources
application.properties
application-dev.properties
application-test.properties
application-prod.properties
application.properties
# Shared by all environments
spring.application.name=shop-service
server.servlet.context-path=/shop
spring.jpa.open-in-view=false
application-dev.properties
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
application-prod.properties
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:

WhereHowExample
Command line (program argument)--spring.profiles.active after the JAR namejava -jar shop.jar --spring.profiles.active=prod
JVM system property-D before -jarjava -Dspring.profiles.active=prod -jar shop.jar
Environment variableSPRING_PROFILES_ACTIVEexport SPRING_PROFILES_ACTIVE=prod
Maven during developmentPlugin property./mvnw spring-boot:run -Dspring-boot.run.profiles=dev
Gradle during developmentProgram argument./gradlew bootRun --args='--spring.profiles.active=dev'
IDE run configurationActive profiles field, or a VM option-Dspring.profiles.active=dev
application.propertiesA fallback valuespring.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 and Kubernetes
# 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:

LocalShopApplication.java
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:

Console
The following 1 profile is active: "dev"

If no profile is set, you see this instead, which is a useful warning in production:

Console
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:

StartupBanner.java
@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:

PaymentGateway.java
public interface PaymentGateway {
    PaymentResult charge(String orderId, BigDecimal amount);
}
FakePaymentGateway.java
@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
    }
}
StripePaymentGateway.java
@Service
@Profile("prod")
public class StripePaymentGateway implements PaymentGateway {

    @Override
    public PaymentResult charge(String orderId, BigDecimal amount) {
        // call the real payment provider
    }
}
CheckoutService.java
@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:

TestDataConfig.java
@Configuration
@Profile("test")
public class TestDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2)
                .addScript("schema.sql")
                .addScript("test-data.sql")
                .build();
    }
}
NotificationConfig.java
@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

ExpressionBean 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:

InMemoryCatalog.java
@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:

application.properties
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:

Terminal
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:

application.properties
spring.profiles.group.production=prod,postgres,kafka
spring.profiles.group.local=dev,h2,mock-payments
Terminal
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:

application.yml
# 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:

logback-spring.xml
<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:

CheckoutServiceTest.java
@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:

application-prod.properties
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 secretsGood for
Environment variablesSimple servers and containers
Kubernetes SecretsApplications 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 caseHow profiles help
Different databases per environmentDatasource settings in each profile file
Fake external services in development@Profile chooses fake or real beans
Log levels and log destinationsProfile files or logback-spring.xml
Sample data only outside productionA data loader marked @Profile("!prod")
Regional differencesCombine 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 @Profile for 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

MistakeWhat goes wrongFix
Typo in a file name, such as application_dev.propertiesThe file is silently ignoredUse a hyphen: application-dev.properties
Committing spring.profiles.active=dev in application.propertiesProduction may start with dev settings if nothing overrides itSet the profile outside the JAR; use spring.profiles.default for local runs
Setting spring.profiles.active inside a profile-specific fileStartup fails with an invalid property errorUse profile groups instead
Misspelled profile name, such as prdNo error, but no profile settings are appliedCheck the “profile is active” line in the log
Using the old spring.profiles: key in YAML documentsNot supported in current Spring Boot versionsUse spring.config.activate.on-profile
Real passwords in profile filesSecrets end up in version controlUse ${...} 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.