Saturday, February 18, 2023

Working with environment variables in Spring Boot

I'm writing this article because I've recently been sorting out how to access environment variables when using Spring Boot.  Environment variables are important because you don't want to be checking in sensitive information such as secrets and keys into source control by adding them directly to properties files.

At a high level, my development environment involves:

  • Spring Boot
  • Eclipse
  • Heroku
The most convenient way for me to develop is to be able to run the Spring Boot application directly in Eclipse, then build and test on the command-line inside a Heroku container before pushing to production in Heroku. What I have found is that it is not at all straightforward how to access environment variables in these different contexts.

There are multiple ways to access environment variables inside a Spring Boot application. Two pretty straightforward ways are to:
  1. Access the environment variable directly inside application code by wiring the values in with the 'Value' annotation; ex. Value("MY_ENVAR") String myEnvar; 
  2. Parameterize values in the application.properties file; ex. my.envar=${MY_ENVAR} 
Example of parameterizing a mongodb url

The mongodb url can contain admin user and password information. This is definitely not something you want to check into source control, so you don't want this information inside your properties files. There are multiple ways you could parameterize the mongodb url. You could consider adding the entire url to an environment variable and accessing it that way. I chose to add a url template the the Spring Boot application.properties file and populate the user name and password inside the url from environment variables. The mondodb connection uri template is as follows:

mongodb+srv://${MONGODB_USER_NAME}:${MONGODB_PASSWORD}@cluster9.asdf.mongodb.net/muhdb?retryWrites=true&w=majority

The question now becomes; how do you access these parameters as environment variables?

Initially, I set the environment variables at the command line in my local development environment.

set MONGODB_USER_NAME=admin
set MONGODB_PASSWORD=asdf

When running the application inside Eclipse, I received the following error:

Exception authenticating MongoCredential{mechanism=SCRAM-SHA-1, userName='${MONGODB_USER_NAME}', source='admin', password=<hidden>, mechanismProperties=<hidden>}

The reason for this, and maybe a very good one, is that Eclipse doesn't access the environment variables on the local system. Instead, it enables setting environment variables in run configurations.

To fix this, go to Run->Run Configurations->Environment, and add environment variables there.

Next, you'll want to be able to build and run a local deployment of the Spring Boot application because this is much closer to the production deployment than running the application in Eclipse. This requires running gradlew to build the application, and heroku local web to bring up the application locally. Each of these has different mechanisms to access environment variables.

'Gradlew build' simply builds the application and runs tests unless explicitly instructed not to. It is a good idea to run the tests on build. The base Spring Boot test case will bring up a test context that will fail if it does not find the environment variables required by the application. This requires the environment variables to be set on the local system. On windows this can be set via 'set MY_ENVAR=asdf'.

Bringing up a local Heroku deployment accesses environment variables differently. The application is deployed and run inside a local Heroku container, so it would not have access to environment variables on the local system. To provide environment variables for the local Heroku container to access, just add them to a .env file in the project base directory. This .env file will be deployed into the local Heroku container where the environment variables are set based on these values. Be sure to add the .env file to your .gitignore file so it won't be checked into source conrol!

When deploying to Heroku production, you need to get the environment variables set inside the remote Heroku container where the application will be running. The remote production container will not reference the .env file on deployment. To accomplish this you can use the Heroku command line; 'heroku config:set MY_ENVAR=asdf'.

Summary:

Primary ways for a Spring Boot application to access environment variables:
  • Directly wired into application code via the "Value" annotation.
  • Parameterized inside the Spring Boot application.properties file.
Where and how to set environment variables:
  • For running builds inside Eclipse: go to Run->Run Configurations->Environment and add environment variables.
  • For builds run with Gradle: set the environment variables in the local environment. On windows this is accomplished with, 'set MY_ENVAR=asdf'.
  • For local Heroku builds: add the environment variables to a .env file.
  • For remote, production Heroku deployments: set the environment variables in the remote container with 'heroku config:set MY_ENVAR=asdf'.
Additional analysis:

It seems like this is a lot of places to have to keep track of environment variables when developing Spring Boot applications for Heroku in Eclipse. It would be nice if a couple of these sources could be merged. Some possibilities are:
  • Extend the gradle eclipse plugin to be able to update the run configuration with environment variables.
  • Use gradle to set local system environment variables, and maybe reference them when running the eclipse plugin to customize the environment variables in the project specific run configuration.
  • Enable gradle and eclipse to read environment variables from an external file, such as .env.

No comments: