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
- Profile never activated: Set
spring.profiles.active=devinapplication.propertiesor via theSPRING_PROFILES_ACTIVEenvironment variable - YAML syntax wrong: Each profile block in
application.ymlneeds---separator andspring.config.activate.on-profile(not the oldspring.profileskey) - @Profile bean not found: Ensure the profile name matches exactly — profiles are case-sensitive
- Wrong file location: Profile-specific files must be in the same directory as
application.properties, on the classpath root orconfig/ - 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
defaultprofile. - Naming mismatch.
application-Dev.propertieswon’t load for profiledevon case-sensitive filesystems. - Wrong YAML key. Spring Boot 2.4+ changed the YAML syntax for profiles. Old
spring.profileskey 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):
./config/(current working directory)./(current working directory)classpath:/config/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:
- Command-line arguments (
--server.port=8081) SPRING_APPLICATION_JSONenvironment variable- OS environment variables (
SERVER_PORT=8081) - JVM system properties (
-Dserver.port=8081) - Profile-specific files (
application-dev.properties) - Application properties (
application.properties) @PropertySourceannotations- 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.