Spring Boot Profiles

1 minute read

Different environments need different configurations — in-memory H2 for dev, PostgreSQL for production, debug logging for dev, minimal logging for prod. Profiles solve this cleanly.


Profile-Specific Configuration Files

src/main/resources/
├── application.yml              ← Shared config (all profiles)
├── application-dev.yml          ← Dev overrides
├── application-staging.yml      ← Staging overrides
└── application-prod.yml         ← Production overrides

application.yml (shared)

app:
  name: Bookstore API
  version: 1.0.0

spring:
  jpa:
    open-in-view: false

application-dev.yml

server:
  port: 8080

spring:
  datasource:
    url: jdbc:h2:mem:devdb
    driver-class-name: org.h2.Driver
  h2:
    console:
      enabled: true
  jpa:
    hibernate:
      ddl-auto: create-drop
    show-sql: true

logging:
  level:
    com.bookstore: DEBUG
    org.hibernate.SQL: DEBUG

application-prod.yml

server:
  port: 8080

spring:
  datasource:
    url: ${DATABASE_URL}
    username: ${DATABASE_USERNAME}
    password: ${DATABASE_PASSWORD}
  jpa:
    hibernate:
      ddl-auto: validate
    show-sql: false

logging:
  level:
    root: WARN
    com.bookstore: INFO

Activating a Profile

# Command line
java -jar app.jar --spring.profiles.active=prod

# Environment variable
SPRING_PROFILES_ACTIVE=prod java -jar app.jar

# In application.yml (default profile)
spring:
  profiles:
    active: dev

@Profile Annotation — Profile-Specific Beans

@Configuration
@Profile("dev")
public class DevConfig {

    @Bean
    public DataLoader dataLoader(BookRepository bookRepository) {
        return new DataLoader(bookRepository);  // seed sample data in dev
    }
}

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

    @Bean
    public DataSource dataSource() {
        // Production-grade connection pool
        HikariDataSource ds = new HikariDataSource();
        ds.setMaximumPoolSize(20);
        ds.setMinimumIdle(5);
        return ds;
    }
}
@Service
@Profile("!prod")  // active in all profiles EXCEPT prod
public class MockPaymentService implements PaymentService {
    public void charge(BigDecimal amount) {
        System.out.println("Mock charge: $" + amount);
    }
}

@Service
@Profile("prod")
public class StripePaymentService implements PaymentService {
    public void charge(BigDecimal amount) {
        // Real Stripe integration
    }
}

Default Profile

If no profile is active, Spring uses the default profile:

@Profile("default")
@Service
public class FallbackService { }

Multiple Active Profiles

java -jar app.jar --spring.profiles.active=prod,metrics,ssl
spring:
  profiles:
    active: dev,swagger

Real-World Setup

Environment     Profile     Database         Logging    Features
──────────────  ──────────  ───────────────  ─────────  ────────
Local dev       dev         H2 in-memory     DEBUG      Swagger, sample data
Staging         staging     PostgreSQL       INFO       Swagger
Production      prod        PostgreSQL       WARN       No swagger, monitoring

Common Mistakes

Mistake Fix
Hardcoding spring.profiles.active=dev Set via env var in production
Secrets in application-prod.yml Use environment variables: ${DB_PASSWORD}
No profile for tests Create application-test.yml or use @ActiveProfiles("test")
ddl-auto=create-drop in production Use validate or none — use Flyway for migrations

Key Takeaways

  1. Profile-specific files: application-{profile}.yml override application.yml.
  2. Activate with --spring.profiles.active=prod or SPRING_PROFILES_ACTIVE.
  3. @Profile creates beans only for specific environments.
  4. Use dev for H2 + debug logging, prod for PostgreSQL + env vars.
  5. Never commit production secrets — use environment variables.

What’s Next?

Next: Building a REST API — your first real Spring Boot CRUD API.

Updated: