How to use .env files with the Azure SDK for Java

UPDATE 5/13/2020 - This is now outdated as of version 5.2.0 of java-dotnet package. A systemProperties method was added to make all of this easier. You can find all of the updated details on the new post here: https://blog.jongallant.com/java-dotenv-azure-sdk-update/

I regularly use .env files to store secrets locally which saves me from having to configure environment variables at the machine level.

Today, if you try to use the Azure SDKs with the java-dotenv package, it will not work because java-dotenv does not write to System environment variables or System properties. The TLDR is at the bottom, so you can scroll down to get the solution to use .env with Azure SDK for Java.

How .env files work

Here’s an example of a .env file.

AZURE_CLIENT_ID=d40990d4
AZURE_CLIENT_SECRET=9eb93866
AZURE_TENANT_ID=63296244-
AZURE_KEYVAULT_URL=https://jongkv2.vault.usgovcloudapi.net/
AZURE_AUTHORITY_HOST=login.microsoftonline.us

I then use a standard .env package to load the values into the currently running process, like this:

import io.github.cdimascio.dotenv.Dotenv;

Dotenv dotenv = Dotenv.load();

dotenv.get("AZURE_CLIENT_ID")

That uses this java-dotenv package: https://github.com/cdimascio/java-dotenv to load the .env file and then read the value from AZURE_CLIENT_ID

The issue with Java and .env files

All other languages I work with also automatically stuff those values into the standard machine environment variables and can be access like this C# example:

var clientId = System.GetEnvironmentVariable("AZURE_CLIENT_ID");

But that doesn’t work in Java. This code…

Dotenv dotenv = Dotenv.load();
String clientId = System.getenv("AZURE_CLIENT_ID");

…will return null for clientId, because the Java library java-dotenv does not stuff environment variables and it doesn’t look like there’s an easy way to do so with Java. I found some hacks, but they were rough. Here’s a link to the java-dotenv FAQ on this: https://github.com/cdimascio/java-dotenv#faq

Q: Why should I use dotenv.get(“MY_ENV_VAR”) instead of System.getenv(“MY_ENV_VAR”)
A: Since Java does not provide a way to set environment variables on a currently running process, vars listed in .env cannot be set and thus cannot be retrieved using System.getenv(…).

The issue with Azure SDK and java-dotenv

If you try to use any of the Azure SDK classes that depend on environment variables, like DefaultAzureCredential, they will not work with java-dotenv out of the box.

This is because java-dotenv does not write to system environment variables or system properties - it stuffs the variables in the Dotenv class and stops there.

The Azure SDK obviously doesn’t know to look for settings in the Dotenv instance.

The solution to using Azure SDK with java-dotenv

Azure SDK for Java reads from both System properties and environment variables

Luckily, the Azure SDK for Java reads from both environment variables and system properties. As you can see in the Configuration.java file, both System::getProperty and System::getenv are implemented as configuration loaders:

/*
    * System property loader.
    */
private static final Function<String, String> PROPERTY_LOADER = System::getProperty;

/*
    * Environment variable loader.
    */
private static final Function<String, String> ENV_VAR_LOADER = System::getenv;

private static final List<Function<String, String>> LOADERS = Arrays.asList(PROPERTY_LOADER, ENV_VAR_LOADER);

And then the Azure SDK configuration object loads from both locations here:

/*
* Attempts to load the configuration from the environment.
*
* The runtime parameters are checked first followed by the environment variables.
*
* @param name Name of the configuration.
* @return If found the loaded configuration, otherwise null.
*/
private static String load(String name) {
    for (Function<String, String> loader : LOADERS) {
        String value = loader.apply(name);
        if (value != null) {
            return value;
        }
    }

    return null;
}

Copy java-dotenv properties to System properties

Because Azure SDK reads from System properties - you can simply copy all of the java-dotenv properties to system properties and then the Azure SDK will read them.

My fellow Azure SDK dev Srikanta wrote the following code to copy all properties from dotenv to system properties. He is going to work with the java-dotenv project to see if we can get this implemented as a feature in the library itself. For now, copy this to your project.

static void loadEnvironmentProperties() {
    Dotenv ENVIRONMENT = Dotenv.load();
    ENVIRONMENT.entries().forEach(entry -> System.setProperty(entry.getKey(), entry.getValue()));
}

Then call loadEnvironmentProperties before you use the Azure SDKs.

loadEnvironmentProperties();

DefaultAzureCredential cred = new DefaultAzureCredentialBuilder().build();

KeyClient keyClient = new KeyClientBuilder().vaultUrl(System.getProperty("AZURE_KEYVAULT_URL"))
                .credential(cred).buildClient();

… and it should just work.

Let me know if you run into any issues.

Jon