00:00

Spring Boot Profiles

Last updated:

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 an application, such as development, testing, and production, without changing the application code.

Profiles help manage environment-specific configurations, such as database connections, server settings, logging levels, external service URLs, etc. You can activate a specific profile when running your application. Spring Boot then loads the configuration associated with the active profile.

You need to create profile-specific configuration files, such as application-dev.properties, application-test.properties, and application-prod.properties, along with application.properties for the default configuration. Then, activate the required profile using the spring.profiles.active property or a command-line argument. If no profile is explicitly activated, Spring Boot uses the default profile to load the appropriate configuration.

2. Why Do We Need Profiles?

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

4. Activating a Profile

You can activate a Spring Boot profile in several ways, depending on the deployment environment and how you want to manage application configuration.

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);
    }
}

5. Checking Which Profiles Are Active

To identify the active profile of a running application, check the application startup logs. Spring Boot displays the active profiles during startup, making it easy to verify which configuration is being used.

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.

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

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

8. 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.

9. 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.

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.

10. 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

---
# Development profile
spring:
  config:
    activate:
      on-profile: dev
server:
  port: 8081
logging:
  level:
    com.javabytechie: DEBUG

---
# Production profile
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.

11. 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.

12. 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.

13. 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.

14. 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.

15. 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

16. 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.