Spring Profiles best practices


TLDR:

  • Make application.properties your base config
  • Create profile-specific application-{profile}.properties files (local, dev, stage, prod)
  • Add common configurations to base profile
  • Add or override environment-specific configurations in env profile
  • Start application only with profile

What is Spring Profiles?

Spring Profiles is a powerful feature that makes your development experience much better by providing you a way to make your application environment-aware

Just run your application like this:

java -jar app.jar -Dspring.profiles.active=dev

And you will see in logs that Spring uses the dev profile. Yours application logic can detect it too!

Please refer to an official Spring documentation to learn more, if you are not already familiar! You can thank me later 😉

Best practices

Anyway, Spring Profiles is cool and stuff but how does this work in real world?

Spring provides you so many features with profiles like: allow to have multiple active profiles, create env-specific beans, set profiles programmatically, have a profile groups, have profile-specific configuration files, etc.

Although there is no right way to use these, eventually found myself using only profile-specific configuration files and will show you today my preferred way of making configuration that serves me well

Structured configuration

Consider that we have some baseline configuration in application.yaml for our backend application to work:

server:
  port: 8090

logging:
  level: debug

spring:
  datasource:
    url: "jdbc:postgresql://localhost:5432/database"
    username: "user"
    password: "secret"

We do not want to deploy it like that, we won’t use our local database as a production one

Let’s use environment variables to allow us to change database credentials:

server:
  port: 8090

logging:
  level: debug

spring:
  datasource:
    url: ${DB_URL}
    username: ${DB_USER}
    password: ${DB_PASSWORD}

That’s better! Now we can build our artifact and deploy it with env variables at our production server. We can also deploy it to testing and everything will work just fine! Why would we even need profiles for?

Now logging file is huge on production server - we forgot to change our logging level from debug to info! That’s a shame, lets fix that!

server:
  port: 8090

logging:
  level: info

spring:
  datasource:
    url: ${DB_URL}
    username: ${DB_USER}
    password: ${DB_PASSWORD}

Now we have less amount of logs everywhere, but we can’t do our DDD - Debug Driven Development without debug logs 😅 Okay, to address that we can change it set it to debug locally and fix this line every time we do commit changes 😎 That’s pretty bad idea, cause people forget things and better approach is to use Spring Profiles.

Create application-local.yaml:

logging:
  level: debug

spring:
  datasource:
    url: "jdbc:postgresql://localhost:5432/database"
    username: "user"
    password: "secret"

File structure will look like this

src/
    main/
      java/
        myapp/
          ...
      resources/
        application-local.yaml
        application.yaml

Now start your application with profile local and observe the port that is allocated by our application

It should be 8090, even if we did not specify it in our local config, why? Spring merged our default config with local one. The resulting configuration is present in runtime! Thus, you can share common configurations between many profiles and also have an opportunity to partially override properties, add specific ones (like env specific logging). And all that is plain texted in one directory with minimal code overlap, how cool is that?

Summary

For the most projects profiles are used only for environment separation. It is recommended by Spring team to use profiles for that purpose, so business logic should not be profile dependent.
Use profile-specific configuration files to accomplish configuration simplicity and flexibility with minimal code overlap.

  • Use only one profile at time
  • Have clear profile naming scheme
  • Try to avoid profile-dependent business logic

Final structure:

src/
    main/
      java/
        myapp/
          ...
      resources/
        application-local.yaml
        application.yaml

application.yaml:

server:
  port: 8090

logging:
  level: info

spring:
  datasource:
    url: ${DB_URL}
    username: ${DB_USER}
    password: ${DB_PASSWORD}

application-local.yaml:

logging:
  level: debug

spring:
  datasource:
    url: "jdbc:postgresql://localhost:5432/database"
    username: "user"
    password: "secret"