367

We are working on a Spring Boot web application, and the database we are using is MySQL;

  • the setup we have is we first test it locally (means we need to install MySQL on our PC);

  • then we push to Bitbucket;

  • Jenkins automatically detects the new push to Bitbucket and does a build on it (for Jenkins mvn build to pass we also need to install MySQL on the virtual machines that is running Jenkins).

  • if Jenkins build passes we push the code to our application on OpenShift (using the Openshift deployment plugin on Jenkins).

The problem we have, as you may have already figured it out, is that:

  • in application.properties we can not hard code the MySQL info. Since our project will be running in 3 different places (local, Jenkins, and OpenShift), we need to make the datasource field dynamic in application.properties (we know there are different ways of doing it but we are working on this solution for now).

      spring.datasource.url = 
      spring.datasource.username = 
      spring.datasource.password = 
    

The solution we came up with is we create system environment variables locally and in the Jenkins VM (naming them the same way OpenShift names them), and assigning them the right values respectively:

export OPENSHIFT_MYSQL_DB_HOST="jdbc:mysql://localhost"
export OPENSHIFT_MYSQL_DB_PORT="3306"
export OPENSHIFT_MYSQL_DB_USERNAME="root"
export OPENSHIFT_MYSQL_DB_PASSWORD="123asd"

We have done this and it works. We have also checked with Map<String, String> env = System.getenv(); that the environment variables can be made into java variables as such:

String password = env.get("OPENSHIFT_MYSQL_DB_PASSWORD");   
String userName = env.get("OPENSHIFT_MYSQL_DB_USERNAME");   
String sqlURL = env.get("OPENSHIFT_MYSQL_DB_HOST"); 
String sqlPort = env.get("OPENSHIFT_MYSQL_DB_PORT");

Now the only thing left is we need to use these java variables in our application.properties, and that is what we are having trouble with.

In which folder, and how, do we need to assign the password, userName, sqlURL, and sqlPort variables for application.properties to be able to see them and how do we include them in application.properties?

We have tried many things one of them being:

spring.datasource.url = ${sqlURL}:${sqlPort}/"nameofDB"
spring.datasource.username = ${userName}
spring.datasource.password = ${password}

No luck so far. We are probably not putting these environment variables in the right class/folder or are using them incorrectly in application.properties.

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
S M
  • 3,699
  • 3
  • 10
  • 5
  • 4
    Read [@ConfigurationProperties](https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html) to learn more. However, this is a perfect use case for [Profile specific configuration properties](https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-profile-specific-properties) – Edward J Beckett Feb 21 '16 at 02:48
  • spring.datasource.url=${nmi_spring_datasource_url} spring.datasource.username=${nmi_spring_datasource_username} spring.datasource.password=${nmi_spring_datasource_password} This worked for me. I added the above 3 environment variables into System Variables. The only weird thing was that I had to close and open my IntelliJ to make Spring boot "read" it. OP: were you able to resolve your issue? – shortduck May 09 '23 at 01:55

10 Answers10

491

You don't need to use java variables. To include system env variables add the following to your application.properties file:

spring.datasource.url = ${OPENSHIFT_MYSQL_DB_HOST}:${OPENSHIFT_MYSQL_DB_PORT}/"nameofDB"
spring.datasource.username = ${OPENSHIFT_MYSQL_DB_USERNAME}
spring.datasource.password = ${OPENSHIFT_MYSQL_DB_PASSWORD}

But the way suggested by @Stefan Isele is more preferable, because in this case you have to declare just one env variable: spring.profiles.active. Spring will read the appropriate property file automatically by application-{profile-name}.properties template.

sadeghpro
  • 448
  • 2
  • 8
  • 24
Ken Bekov
  • 13,696
  • 3
  • 36
  • 44
  • 1
    can you please give a link to the reference fragment about using env variables in the application.properties files? – Alex Dvoretsky Apr 12 '16 at 05:49
  • 4
    Found my self http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-external-config-placeholders-in-properties – Alex Dvoretsky Apr 12 '16 at 05:56
  • 19
    This method is more convenient for docker linking. For instance: `docker run --name my-tomcat -p 127.0.0.1:8080:8080 -e APP_DB_DB=mydb -e APP_DB_USER=dbuser -e APP_DB_PASS=dbpass --link mongo-myapp:mongo -v /path-to/tomcat/webapps:/usr/local/tomcat/webapps -d tomcat:8-jre8-alpine` – Fırat Küçük Nov 28 '16 at 15:47
  • 32
    This is absolutely the best way to go. Using environment variables means you don't need to list secrets in plain text along side your application. This is significantly more secure and reduces dependency on your source code access security measures to protect your entire estate. An accidental SO post with properties included doesn't result in information leaking out. – kipper_t Feb 23 '17 at 08:38
  • 99
    I wanted to add to this and mention that if you are using spring boot(didn't check if it works without boot), then any property can be overriden via an environment variable automatically without modifying your application.properties. ie, if you have a property called `spring.activemq.broker-url` then the corresponding environment variable would be: `SPRING_ACTIVEMQ_BROKER_URL`. periods and dashes are automatically converted to underscores. This is extremely convenient when working with containers/spring boot. – abe Dec 15 '17 at 18:52
  • it is spring.profiles.active, not spring.active.profiles – Kevin Hawk Jan 11 '18 at 08:11
  • 20
    If you design for cloud it's not a preferable way to use Spring profiles. Using environment variables is recommended by the 12 factor app standard: https://12factor.net/config – Mikhail Golubtsov Jun 29 '18 at 09:27
  • @Carmageddon as far as I know, Spring cloud bootstrap phase happens before ApplicationContext is loaded. So Spring simply can't add vars from `bootstrap.properties` to the `Environment`. – Ken Bekov Feb 01 '19 at 04:14
  • 7
    I know this topic is a bit old. But you can combine both environment variable setup and spring profile setup. You dev profile should have static information while your production profile can make use of the environment variables. In this way dev's no longer need to define environment variables on their machine if they just want to deploy the development profile. – underscore_05 Feb 19 '19 at 16:57
  • 3
    @abe thanks for in the information. I checked the spring doc and it depends on how do you inject environment variables. according to https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-vs-value, `@value` doesn't support the flexible binding but `ConfigurationProperties ` can do as what you described. – danny Mar 01 '19 at 02:30
  • 1
    This doesn't works for me, for some reason my spring app is trying to reach this route: ${mongo_port}:27017 instead of localhost:27017 – Agustín Clemente May 15 '19 at 12:39
  • 1
    Does the environment variables resolve in windows based environment? The syntax for environment variable seems like linux. – Master Chief Aug 06 '19 at 03:36
  • 1
    @Master Chief yes it does – Ken Bekov Aug 06 '19 at 03:59
  • @underscore_05 Yup, that's how I'm doing it – GabrielBB Sep 15 '19 at 01:23
  • 1
    @abe I think that only works for Spring own properties and not your custom properties – GabrielBB Sep 15 '19 at 01:25
  • 3
    There's an unfortunate gotcha with this: If Spring Boot can't find the environment variable specified by the placeholder, it silently leaves the placeholder unexpanded. – antak Oct 29 '19 at 08:45
  • What about when datasource is MongoDB ReplicaSet and I need to pass X-number of members? – JackTheKnife Jan 25 '21 at 17:48
  • Nice thanks. How can I use the ${DB_PASSWORD} but make it encrypted – ACV Nov 30 '21 at 19:01
  • @antak Your comment gave me a hint that led me to finally solve my issue after 2 days of debugging. I checked the logs and found it attempting to log in with the unexpanded variable and going by your comment, I knew they weren't being picked up. Turns out I needed to restart WebLogic and not just redeploy the application in order for my newly set env variables to be made available to applications running in the container. – David Feb 24 '23 at 08:36
  • How can we place environment variables in a file and have Spring load them into the environment? – Kevin Wheeler Jun 17 '23 at 14:30
114

The easiest way to have different configurations for different environments is to use spring profiles. See externalised configuration.

This gives you a lot of flexibility. I am using it in my projects and it is extremely helpful. In your case you would have 3 profiles: 'local', 'jenkins', and 'openshift'

You then have 3 profile specific property files: application-local.properties, application-jenkins.properties, and application-openshift.properties

There you can set the properties for the regarding environment. When you run the app you have to specify the profile to activate like this: -Dspring.profiles.active=jenkins

Edit

According to the spring doc you can set the system environment variable SPRING_PROFILES_ACTIVE to activate profiles and don't need to pass it as a parameter.

is there any way to pass active profile option for web app at run time ?

No. Spring determines the active profiles as one of the first steps, when building the application context. The active profiles are then used to decide which property files are read and which beans are instantiated. Once the application is started this cannot be changed.

DwB
  • 37,124
  • 11
  • 56
  • 82
  • 5
    I like this answer, but what if you want the profile name to come from the environment? I've tried -Dspring.active.profiles=$SPRING_ACTIVE_PROFILES, and setting the OS env var in /etc/profile.d/myenvvars.sh, but Spring Boot doesn't pick that up – Tom Hartwell May 23 '16 at 18:47
  • 2
    SPRING_PROFILES_ACTIVE works because of the relaxed binding feature of spring boot http://docs.spring.io/spring-boot/docs/1.3.0.BUILD-SNAPSHOT/reference/htmlsingle/#boot-features-external-config-relaxed-binding – Fabian Jul 19 '16 at 14:24
  • 5
    thanks for this answer Stefan, it worked for me, but with one change - the property is actually spring.profiles.active and not spring.active.profiles – Rudi Aug 11 '16 at 09:38
  • is there any way to pass active profile option for web app at run time ? Default to production env but should be able to activate dev, staging etc at runtime (e.g. through query parameter) – vishal Oct 05 '16 at 13:52
  • 19
    Whilst Spring profiles can be very useful, in relation to the OP they aren't suitable. This is due to how source code is stored and sensitivity of the properties information stored with that. The OP context is around Database access. For that situation you don't want prod details in plain text in source. This means if the source is compromised then the database is also compromised. It is better to use env variables or secret tools for this instead such as Vault. I prefer env. I'd also make all environments operate the same way in this regards for consistency. It avoids accidents in the future. – kipper_t Feb 23 '17 at 08:37
  • 2
    You can use a Spring Boot profile properties file external to the application JAR. This environment-specific file, for instance, `application-production.properties`, would be deployed to the production machine in a secure way, and would not typically be in the application source code repository. – Colin D Bennett Nov 27 '18 at 20:03
  • Spring profiles do not solve the problem of having different set of database credentials - you won't store them in plain text anyway. Also spring profiles approach (though being useful before) is less and less relevant in containerized world, you don't want to create different images for different environments – Daniil Apr 23 '21 at 17:03
  • Putting configuration of your external servers inside the codebase is not a good idea. The codebase should be clean of external server configuration for security. Environment variables are safer because, especially for username and password, in a cloud environment like OKD or OpenShift, they can be stored encrypted using a "secret", so only the administrator can set the appropriate values, not the developer. – apetrelli Sep 08 '21 at 08:28
46

Flyway doesn't recognize the direct environment variables into the application.properties (Spring-Boot V2.1). e.g

spring.datasource.url=jdbc:mysql://${DB_HOSTNAME}:${DB_PORT}/${DB_DATABASE}
spring.datasource.username=${DB_USER}
spring.datasource.password=${DB_PASS}

To solve this issue I did this environment variables, usually I create the file .env:

SPRING_DATASOURCE_URL=jdbc:mysql://127.0.0.1:3306/place
SPRING_DATASOURCE_USERNAME=root
SPRING_DATASOURCE_PASSWORD=root

And export the variables to my environment:

export $(cat .env | xargs)

And finally just run the command

mvn spring-boot:run

Or run your jar file

java -jar target/your-file.jar

There another approach here: https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/maven-plugin/examples/run-env-variables.html

Felipe Girotti
  • 571
  • 4
  • 6
  • 3
    What is env-vars? How are they used. Your answer refers to things without a complete description and you do not include any links. I almost downvoted this, but I see your rep is 21 so you are new and one person found your answer useful, so I let it go, but try to provide more information in future answers, and welcome to SO (Stack Overflow). I hope you enjoy it as much as I do. – PatS Jan 26 '19 at 18:29
  • 2
    Thanks @PatS, I added more details, hope it will useful. – Felipe Girotti Jan 29 '19 at 18:46
  • 1
    Excellent changes. Thanks updating your answer. – PatS Jan 30 '19 at 04:07
  • Link returns 404 – Raj Apr 26 '21 at 02:00
  • 1
    Where would you put this command? `export $(cat .env | xargs)` – levininja Jul 20 '23 at 21:32
16

This is in response to a number of comments as my reputation isn't high enough to comment directly.

You can specify the profile at runtime as long as the application context has not yet been loaded.

// Previous answers incorrectly used "spring.active.profiles" instead of
// "spring.profiles.active" (as noted in the comments).
// Use AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME to avoid this mistake.

System.setProperty(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, environment);
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/META-INF/spring/applicationContext.xml");
gthazmatt
  • 161
  • 1
  • 6
14

Here is a snippet code through a chain of environments properties files are being loaded for different environments.

Properties file under your application resources ( src/main/resources ):-

 1. application.properties
 2. application-dev.properties
 3. application-uat.properties
 4. application-prod.properties

Ideally, application.properties contains all common properties which are accessible for all environments and environment related properties only works on specifies environment. therefore the order of loading these properties files will be in such way -

 application.properties -> application.{spring.profiles.active}.properties.

Code snippet here :-

    import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.core.io.Resource;

    public class PropertiesUtils {

        public static final String SPRING_PROFILES_ACTIVE = "spring.profiles.active";

        public static void initProperties() {
            String activeProfile = System.getProperty(SPRING_PROFILES_ACTIVE);
            if (activeProfile == null) {
                activeProfile = "dev";
            }
            PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer
                    = new PropertySourcesPlaceholderConfigurer();
            Resource[] resources = new ClassPathResource[]
                    {new ClassPathResource("application.properties"),
                            new ClassPathResource("application-" + activeProfile + ".properties")};
            propertySourcesPlaceholderConfigurer.setLocations(resources);

        }
    }
Ajay Kumar
  • 4,864
  • 1
  • 41
  • 44
  • 4
    Doesn't Spring Boot handle this scenario out of the box? See [External Config documentation here](https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-external-config.html#boot-features-external-config-profile-specific-properties) – ChickenFeet Sep 05 '19 at 02:10
8

I faced the same issue as the author of the question. For our case answers in this question weren't enough since each of the members of my team had a different local environment and we definitely needed to .gitignore the file that had the different db connection string and credentials, so people don't commit the common file by mistake and break others' db connections.

On top of that when we followed the procedure below it was easy to deploy on different environments and as en extra bonus we didn't need to have any sensitive information in the version control at all.

Getting the idea from PHP Symfony 3 framework that has a parameters.yml (.gitignored) and a parameters.yml.dist (which is a sample that creates the first one through composer install),

I did the following combining the knowledge from answers below: https://stackoverflow.com/a/35534970/986160 and https://stackoverflow.com/a/35535138/986160.

Essentially this gives the freedom to use inheritance of spring configurations and choose active profiles through configuration at the top one plus any extra sensitive credentials as follows:

application.yml.dist (sample)

    spring:
      profiles:
        active: local/dev/prod
      datasource:
        username:
        password:
        url: jdbc:mysql://localhost:3306/db?useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8

application.yml (.gitignore-d on dev server)

spring:
  profiles:
    active: dev
  datasource:
    username: root
    password: verysecretpassword
    url: jdbc:mysql://localhost:3306/real_db?useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8

application.yml (.gitignore-d on local machine)

spring:
  profiles:
    active: dev
  datasource:
    username: root
    password: rootroot
    url: jdbc:mysql://localhost:3306/xampp_db?useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8

application-dev.yml (extra environment specific properties not sensitive)

spring:
  datasource:
    testWhileIdle: true
    validationQuery: SELECT 1
  jpa:
    show-sql: true
    format-sql: true
    hibernate:
      ddl-auto: create-drop
      naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL57InnoDBDialect

Same can be done with .properties

Michail Michailidis
  • 11,792
  • 6
  • 63
  • 106
5

Using Spring context 5.0 I have successfully achieved loading correct property file based on system environment via the following annotation

@PropertySources({
    @PropertySource("classpath:application.properties"),
    @PropertySource("classpath:application-${MYENV:test}.properties")})

Here MYENV value is read from system environment and if system environment is not present then default test environment property file will be loaded, if I give a wrong MYENV value - it will fail to start the application.

Note: for each profile, you want to maintain - you will need to make an application-[profile].property file and although I used Spring context 5.0 & not Spring boot - I believe this will also work on Spring 4.1

Abdeali Chandanwala
  • 8,449
  • 6
  • 31
  • 45
4

Maybe I write this too late, but I have gotten the similar problem when I have tried to override methods for reading properties.

My problem have been: 1) Read property from env if this property has been set in env 2) Read property from system property if this property have been setted in system property 3) And last, read from application properties.

So, for resolving this problem I go to my bean configuration class

@Validated
@Configuration
@ConfigurationProperties(prefix = ApplicationConfiguration.PREFIX)
@PropertySource(value = "${application.properties.path}", factory = PropertySourceFactoryCustom.class)
@Data // lombok
public class ApplicationConfiguration {

    static final String PREFIX = "application";

    @NotBlank
    private String keysPath;

    @NotBlank
    private String publicKeyName;

    @NotNull
    private Long tokenTimeout;

    private Boolean devMode;

    public void setKeysPath(String keysPath) {
        this.keysPath = StringUtils.cleanPath(keysPath);
    }
}

And overwrite factory in @PropertySource. And then I have created my own implementation for reading properties.

    public class PropertySourceFactoryCustom implements PropertySourceFactory {

        @Override
        public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
            return name != null ? new PropertySourceCustom(name, resource) : new PropertySourceCustom(resource);
        }


    }

And created PropertySourceCustom

public class PropertySourceCustom extends ResourcePropertySource {


    public LifeSourcePropertySource(String name, EncodedResource resource) throws IOException {
        super(name, resource);
    }

    public LifeSourcePropertySource(EncodedResource resource) throws IOException {
        super(resource);
    }

    public LifeSourcePropertySource(String name, Resource resource) throws IOException {
        super(name, resource);
    }

    public LifeSourcePropertySource(Resource resource) throws IOException {
        super(resource);
    }

    public LifeSourcePropertySource(String name, String location, ClassLoader classLoader) throws IOException {
        super(name, location, classLoader);
    }

    public LifeSourcePropertySource(String location, ClassLoader classLoader) throws IOException {
        super(location, classLoader);
    }

    public LifeSourcePropertySource(String name, String location) throws IOException {
        super(name, location);
    }

    public LifeSourcePropertySource(String location) throws IOException {
        super(location);
    }

    @Override
    public Object getProperty(String name) {

        if (StringUtils.isNotBlank(System.getenv(name)))
            return System.getenv(name);

        if (StringUtils.isNotBlank(System.getProperty(name)))
            return System.getProperty(name);

        return super.getProperty(name);
    }
}

So, this has helped me.

June7
  • 19,874
  • 8
  • 24
  • 34
  • This seems really complex. Are you sure you aren't doing something the hard way, that Spring intended to make easy? – MarkHu Jun 07 '22 at 01:25
3

If the properties files are externalized as environment variables following run configuration can be added into IDE:

--spring.config.additional-location={PATH_OF_EXTERNAL_PROP}

Panwen Wang
  • 3,573
  • 1
  • 18
  • 39
Mahesh K
  • 31
  • 1
0

Simply add a dependency spring-dotenv to your build.gradle or pom.xml.

dependencies {
    implementation 'me.paulschwarz:spring-dotenv:4.0.0'
}
<dependency>
    <groupId>me.paulschwarz</groupId>
    <artifactId>spring-dotenv</artifactId>
    <version>4.0.0</version>
</dependency>
  • Then simply write the secrets in .env as a key value pair.And use it as below
spring:
  data:
    mongodb:
      uri: ${URL}
      username: ${USERNAME}
      password: ${PASSWORD}
      database: ${DATABASE}

If the version is older then write as

    spring:
      data:
          mongodb:
              uri: ${env.URL}
              username: ${env.USERNAME}
              password: ${env.PASSWORD}
              database: ${env.DATABASE}

Check out complete usage or project on GitHub