Spring Profiles feel intuitive — create application-dev.properties, set an active profile, done. Except the failure mode is brutal: Spring Boot starts without errors, loads the wrong environment config, and your app misbehaves silently. No stack trace, no warning. Just wrong behavior.

TLDR — Most Common Fixes

  1. Profile never activated: Set spring.profiles.active=dev in application.properties or via the SPRING_PROFILES_ACTIVE environment variable
  2. YAML syntax wrong: Each profile block in application.yml needs --- separator and spring.config.activate.on-profile (not the old spring.profiles key)
  3. @Profile bean not found: Ensure the profile name matches exactly — profiles are case-sensitive
  4. Wrong file location: Profile-specific files must be in the same directory as application.properties, on the classpath root or config/
  5. Property override not working: Check precedence — environment variables beat property files, command-line args beat both

Why Spring Profiles Silently Fail

Spring Boot’s profile system has two moving parts: activation (which profiles are active) and contribution (what configuration each profile adds). A failure in either part produces no error — Spring just falls back to defaults.

The common culprits:

  • Activation never happened. The profile was never set, so Spring uses the default profile.
  • Naming mismatch. application-Dev.properties won’t load for profile dev on case-sensitive filesystems.
  • Wrong YAML key. Spring Boot 2.4+ changed the YAML syntax for profiles. Old spring.profiles key is silently ignored in newer versions.
  • Classpath placement. The file exists but isn’t on the classpath, or is in a subdirectory that Spring doesn’t scan.
  • Property source precedence. A higher-priority source (env var, command-line) is overriding your profile’s values.

Let’s fix each scenario.


Scenario 1: Profile Never Activates

This is the most common issue. You created application-dev.properties but nothing in it takes effect.

The problem:

# application.properties — profile activation is missing entirely
server.port=8080
spring.datasource.url=jdbc:h2:mem:testdb

Spring Boot defaults to the default profile when nothing else is set. Your application-dev.properties is on the classpath but never loaded.

Fix — activate via application.properties:

# application.properties
spring.profiles.active=dev
server.port=8080
# application-dev.properties
spring.datasource.url=jdbc:postgresql://localhost:5432/myapp_dev
spring.datasource.username=dev_user
spring.datasource.password=dev_pass
logging.level.com.example=DEBUG

Fix — activate via environment variable (recommended for production):

# Linux/macOS
export SPRING_PROFILES_ACTIVE=prod

# Docker / docker-compose
environment:
  - SPRING_PROFILES_ACTIVE=prod

# JVM argument at startup
java -jar -Dspring.profiles.active=prod myapp.jar

The environment variable approach is better for deployed environments because it keeps environment-specific config out of source control.

Verify the active profile is loading:

@SpringBootApplication
public class MyApplication implements ApplicationListener<ApplicationReadyEvent> {

    @Autowired
    private Environment env;

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        System.out.println("Active profiles: " +
            Arrays.toString(env.getActiveProfiles()));
    }

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

Or just check your startup logs — Spring Boot 2.x+ logs The following 1 profile is active: "dev" when a profile is active.


Scenario 2: YAML Profile Syntax (The Silent Breaker)

YAML-based configuration trips up even experienced Spring developers because the syntax changed between Spring Boot 2.3 and 2.4.

❌ Old syntax (broken in Spring Boot 2.4+):

# application.yml — this DOES NOT work in Spring Boot 2.4+
spring:
  profiles: dev   # <-- deprecated, silently ignored

server:
  port: 8081

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/myapp_dev

Spring Boot 2.4 changed how multi-document YAML files work. The spring.profiles key was replaced with spring.config.activate.on-profile. Using the old key means the profile block is parsed but never conditionally applied — all YAML documents load regardless of active profile.

✅ New syntax (Spring Boot 2.4+):

# application.yml
server:
  port: 8080

spring:
  datasource:
    url: jdbc:h2:mem:testdb

---
spring:
  config:
    activate:
      on-profile: dev

  datasource:
    url: jdbc:postgresql://localhost:5432/myapp_dev
    username: dev_user
    password: dev_pass

logging:
  level:
    com.example: DEBUG

---
spring:
  config:
    activate:
      on-profile: prod

  datasource:
    url: jdbc:postgresql://prod-host:5432/myapp
    username: ${DB_USER}
    password: ${DB_PASS}

Each --- separator starts a new YAML document. The first document has no on-profile — it’s always loaded. Subsequent documents apply only when the matching profile is active.

If you’re still on Spring Boot 2.3 or earlier, the old syntax still works:

# Spring Boot 2.3 and earlier
spring:
  profiles: dev
server:
  port: 8081

Check your Spring Boot version in pom.xml or build.gradle to know which syntax applies.


Scenario 3: @Profile Beans Not Being Created

You annotated a @Configuration class or @Bean method with @Profile, but the bean is either always created or never created.

The problem:

// ❌ Typo or case mismatch — "Dev" vs "dev"
@Configuration
@Profile("Dev")   // profile name is case-sensitive!
public class DevDataSourceConfig {

    @Bean
    public DataSource dataSource() {
        return DataSourceBuilder.create()
            .url("jdbc:h2:mem:testdb")
            .build();
    }
}

If your active profile is dev (lowercase) but the annotation says "Dev", the bean is silently skipped. Spring Boot won’t warn you — it just won’t create that bean. If another DataSource bean exists (e.g., from auto-configuration), that one loads instead.

✅ Fix — match case exactly:

@Configuration
@Profile("dev")   // matches spring.profiles.active=dev
public class DevDataSourceConfig {

    @Bean
    public DataSource dataSource() {
        return DataSourceBuilder.create()
            .url("jdbc:h2:mem:testdb")
            .driverClassName("org.h2.Driver")
            .build();
    }
}

@Configuration
@Profile("prod")
public class ProdDataSourceConfig {

    @Bean
    public DataSource dataSource() {
        // Production datasource loaded from environment variables
        return DataSourceBuilder.create()
            .url(System.getenv("DB_URL"))
            .username(System.getenv("DB_USER"))
            .password(System.getenv("DB_PASS"))
            .build();
    }
}

Negation and compound profiles:

// Active when "dev" is NOT active
@Profile("!dev")
@Component
public class ProductionEmailService implements EmailService { ... }

// Active when BOTH "cloud" and "prod" are active (Spring 5.1+)
@Profile("cloud & prod")
@Component
public class CloudStorageService implements StorageService { ... }

// Active when either "dev" or "test" is active
@Profile("dev | test")
@Component
public class MockPaymentGateway implements PaymentGateway { ... }

Diagnosing which beans were created:

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        ApplicationContext ctx = SpringApplication.run(MyApplication.class, args);

        // List all beans in context — useful for debugging profile issues
        String[] beanNames = ctx.getBeanDefinitionNames();
        Arrays.sort(beanNames);
        for (String name : beanNames) {
            System.out.println(name);
        }
    }
}

Or add --debug to your startup arguments to get a full auto-configuration report that shows which beans were created and which were excluded.


Scenario 4: Profile-Specific Property File Not Found

You created the file, set the active profile, but Spring Boot still doesn’t load it.

Common file placement mistakes:

src/
  main/
    resources/
      config/
        application-dev.properties   ← Spring WILL find this
      application.properties         ← Spring WILL find this
      application-dev.properties     ← Spring WILL find this
      subdir/
        application-dev.properties   ← Spring WON'T find this

Spring Boot searches for profile-specific files in this order (highest priority first):

  1. ./config/ (current working directory)
  2. ./ (current working directory)
  3. classpath:/config/
  4. classpath:/

Files deeper in the classpath (like classpath:/subdir/) are not searched.

Naming convention — these work:

application-dev.properties
application-dev.yml
application-dev.yaml

These do NOT work:

application_dev.properties   # underscore instead of hyphen
application.dev.properties   # dot separator
dev-application.properties   # prefix before "application"

Multiple active profiles:

# Activate multiple profiles at once
spring.profiles.active=dev,local-db,feature-flags

When multiple profiles are active, Spring loads all matching property files. Later profiles in the list have higher priority — application-local-db.properties overrides application-dev.properties for any duplicate keys.


Scenario 5: Property Override Not Working

Your profile properties are loading, but the values aren’t what you expect. This is a precedence issue.

Spring Boot property sources, from highest to lowest priority:

  1. Command-line arguments (--server.port=8081)
  2. SPRING_APPLICATION_JSON environment variable
  3. OS environment variables (SERVER_PORT=8081)
  4. JVM system properties (-Dserver.port=8081)
  5. Profile-specific files (application-dev.properties)
  6. Application properties (application.properties)
  7. @PropertySource annotations
  8. Default values

Common trap: An environment variable set in your shell overrides application-dev.properties. If DATABASE_URL is set in your shell, your profile’s spring.datasource.url won’t take effect.

Debugging property sources:

Add the Spring Boot Actuator dependency and hit /actuator/env:

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
# application-dev.properties
management.endpoints.web.exposure.include=env
management.endpoint.env.show-values=always
curl http://localhost:8080/actuator/env | jq '.propertySources'

The response shows every property source and its values in priority order — you can see exactly where each property is coming from.

Alternatively, log it at startup:

@Component
public class PropertySourceLogger {

    @Autowired
    private ConfigurableEnvironment env;

    @EventListener
    public void onApplicationReady(ApplicationReadyEvent event) {
        for (PropertySource<?> source : env.getPropertySources()) {
            System.out.println("Source: " + source.getName());
        }
    }
}

Prevention Tips

1. Always log the active profile at startup. Add this to every project’s main class or a @Configuration:

@Slf4j
@Configuration
public class ProfileLogger {

    @Value("${spring.profiles.active:default}")
    private String activeProfile;

    @PostConstruct
    public void logActiveProfile() {
        log.info("Active Spring profile: {}", activeProfile);
    }
}

2. Add a profile guard for production. Prevent accidentally running production config locally:

@Configuration
@Profile("prod")
public class ProdSafetyCheck {

    @PostConstruct
    public void verify() {
        String dbUrl = System.getenv("DATABASE_URL");
        if (dbUrl == null || dbUrl.contains("localhost")) {
            throw new IllegalStateException(
                "Production profile active but DATABASE_URL points to localhost. Aborting.");
        }
    }
}

3. Use @ActiveProfiles in tests. Don’t rely on environment configuration in your test suite:

@SpringBootTest
@ActiveProfiles("test")
class UserServiceIntegrationTest {

    @Autowired
    private UserService userService;

    @Test
    void shouldCreateUser() {
        // Uses application-test.properties configuration
    }
}

4. Validate required properties per profile. Use @ConfigurationProperties with @Validated so missing profile properties fail fast:

@ConfigurationProperties(prefix = "app.database")
@Validated
@Data
public class DatabaseProperties {

    @NotBlank
    private String url;

    @NotBlank
    private String username;

    @NotBlank
    private String password;
}

This way, if you activate prod but forget to set app.database.url, the application fails at startup with a clear BindValidationException rather than silently using a null value.


Still Not Loading?

If none of the above fixes work, try these checks:

Check your Spring Boot version. The profile YAML syntax changed in 2.4. Run:

mvn spring-boot:help -Ddetail=true | grep "Spring Boot"
# or
./gradlew bootRun --info | grep "Spring Boot"

Verify the file is on the classpath. After building:

# Check if the file made it into the JAR
jar tf target/myapp.jar | grep "application-dev"

If it’s not in the JAR, check your pom.xml resource filtering configuration.

Rule out a cached build. Old compiled files can mask your changes:

mvn clean spring-boot:run -Dspring-boot.run.profiles=dev
# or
./gradlew clean bootRun --args='--spring.profiles.active=dev'

Test with a minimal reproduction. Comment out everything in your profile file except one property (like server.port). If that property still doesn’t change, the file isn’t loading. If it does, narrow down which property is causing the conflict.


Summary

Spring Boot profile issues almost always come down to one of five root causes:

Cause Symptom Fix
Profile never activated Default config loads Set spring.profiles.active in properties or env var
Wrong YAML syntax All YAML documents always load Use spring.config.activate.on-profile (Boot 2.4+)
@Profile case mismatch Bean not found or wrong bean injected Match profile name case exactly
File in wrong location Profile file ignored Put files in classpath root or config/
Override by higher source Profile value not applied Check env vars and command-line args

Profile problems are tricky because Spring Boot’s resilience is also its silence — it won’t crash when a profile fails to activate. If your application-dev.properties isn’t loading, you won’t know until you check the active profiles explicitly.

When a profile failure causes a NoSuchBeanDefinitionException or unexpected NullPointerException, use Debugly’s trace formatter to parse the Java stack trace and identify exactly which bean failed to initialize.

For related Spring configuration issues, see the guide on Spring BeanCreationException — many profile failures ultimately surface as bean creation errors. If a missing @Profile bean causes an injection failure, Spring @Autowired null pointer fixes covers the injection side of that problem.