What Are Environment Variables in Java?
Environment variables in Java are external settings that influence system behaviors and configurations. They are useful for segregating environment-specific details from application code. These variables serve as parameters passed to the Java runtime environment, affecting its behavior or configurations without modifying the code.
Operating systems like Windows, Linux, and macOS support these variables, allowing Java programs to adapt to different environments.
Environment variables are often used for managing project settings such as database configurations, API keys, and service endpoints. They avoid hardcoding sensitive data directly into the source code, increasing security and flexibility. This makes it easier to update settings without redeploying or altering the application’s codebase.
In this article:
Use Cases for Environment Variables in Java
There are several ways you can use environment variables in Java.
Storing API Keys
Environment variables are useful for securely storing sensitive information such as API keys. By keeping these keys out of the source code, developers ensure the security of their applications. Instead of hardcoding an API key directly in the code, it can be stored in an environment variable like API_KEY
.
In a Java application, you can access this key with:
String apiKey = System.getenv("API_KEY");
This approach ensures the API key is not embedded in the source code and not exposed in version control systems. Additionally, it allows easy updates to the key without modifying the codebase, making it possible to use different keys in different environments.
Defining Program Arguments
Environment variables can define program arguments, allowing applications to adapt their behavior based on the execution context. For example, a variable like ENV_MODE
can indicate whether the application is running in development, testing, staging, or production mode:
String envMode = System.getenv("ENV_MODE");
In development mode, the application might enable verbose logging and debugging tools. In production, these features are typically disabled to enhance performance and security. This helps manage different configurations and behaviors dynamically, improving maintainability.
Setting Path Variables
Environment variables are also useful for setting path variables such as LOG_PATH
or CONFIG_DIR
. These variables specify directory paths for logging, configuration files, or other critical purposes. This flexibility allows applications to adapt to different environments without modifying the code:
String logPath = System.getenv("LOG_PATH");
For example, in a Java application, reading a configuration path from an environment variable allows it to adapt quickly to different file system layouts across development and production servers or different operating systems. This approach is particularly beneficial for applications that need to run on multiple platforms, where directory structures can vary.
Tips From the Expert
In my experience, here are tips that can help you better manage environment variables in Java:
-
Use external configuration tools for sensitive data
For production, avoid storing sensitive values like API keys in environment variables. Use external configuration management tools like HashiCorp Vault or AWS Secrets Manager to securely store and inject these secrets at runtime, enhancing security. -
Utilize Java system properties as an alternative
When environment variables are not an option, use Java system properties (-Dproperty=value
) to pass configuration values. This allows more flexibility in setting and overriding properties at runtime, especially in complex environments. -
Create fallback mechanisms for missing variables
Always implement fallbacks for missing environment variables by providing defaults or throwing descriptive exceptions. This ensures your Java application behaves predictably in different environments and avoids runtime failures due to misconfigured environments. -
Use dependency injection frameworks
Combine frameworks like Spring or Micronaut with environment variables to inject configuration values automatically. This simplifies your code by abstracting away direct calls toSystem.getenv()
, promoting cleaner and more maintainable configurations. -
Avoid environment variable mutation at runtime
Environment variables should be treated as immutable during application runtime. Changing them dynamically can cause inconsistent behavior, especially when other parts of the system are already using those variables. Stick to setting variables at startup.
Accessing Environment Variables in Java
You can access environment variables in Java using the System
class provided by the standard Java library. Use the System.getenv(String name)
method to retrieve the value of a specific environment variable.
Accessing an environment variable
For example, to access an environment variable named API_KEY
, you would use:
String apiKey = System.getenv("API_KEY");
Handling missing values
It’s a good practice to check if the environment variable is null (i.e., not set) to avoid a potential NullPointerException
. For example:
String apiKey = Optional.ofNullable(System.getenv("API_KEY")).orElse("defaultValue");
Alternatively, you can throw an exception if the environment variable is missing:
Optional.ofNullable(System.getenv("API_KEY"))
.orElseThrow(() -> new IllegalStateException("API_KEY environment variable is not defined"));
Obtaining a map of all environment variables
To get a Map<String, String>
of all environment variables, use System.getenv()
. This is useful if you want to iterate over all available environment variables. For example:
Map envVars = System.getenv();
for (String envName : envVars.keySet()) {
System.out.format("%s=%s%n", envName, envVars.get(envName));
}
Alternatively, this can be written in one line:
System.getenv().forEach((k, v) -> System.out.println(k + "=" + v));
Setting Environment Variables in Java
There are several ways to set environment variables in Java.
1. Using JUnit Pioneer Library
JUnit Pioneer is an extension pack for JUnit, a popular open source Java test framework. You can use JUnit Pioneer to easily set environment variables:
import org.junit.jupiter.api.Test;
import org.junitpioneer.jupiter.SetEnvironmentVariable;
@Test
@SetEnvironmentVariable(key = "MY_ENV_VARIABLE", value = "MY_ENV_VARIABLE_VALUE")
void givenVariableSet_whenGetEnvironmentVariable_thenReturnsCorrectValue() {
String actual = System.getenv("MY_ENV_VARIABLE");
assertThat(actual).isEqualTo("MY_ENV_VARIABLE_VALUE");
}
This method uses the @SetEnvironmentVariable
annotation to set the environment variable for the test method, making it easier to manage environment variables during testing.
2. Using JNI
Another method is to use the Java Native Interface (JNI) to set environment variables. This involves writing native code in C or C++ to interact with the operating system’s environment. Note that this approach has limitations such as platform dependencies and potential issues with Java runtime caching environment variables.
Here’s a basic example:
#include
#include
JNIEXPORT void JNICALL Java_SetEnv_setEnv(JNIEnv *env, jobject obj, jstring key, jstring value) {
const char *nativeKey = (*env)->GetStringUTFChars(env, key, 0);
const char *nativeValue = (*env)->GetStringUTFChars(env, value, 0);
setenv(nativeKey, nativeValue, 1);
(*env)->ReleaseStringUTFChars(env, key, nativeKey);
(*env)->ReleaseStringUTFChars(env, value, nativeValue);
}
This JNI function sets an environment variable using the setenv
function from the C standard library.
The full process of setting environment variables with JNI is complex and outside our scope—it involves multiple steps including creating the Java class file, generating the header file, compiling C code into a shared library, and executing the Java main file.
3. Setting Variables for a Child Process
Using the ProcessBuilder
class in Java, you can set environment variables for a child process. Here’s how you can do it:
import java.io.IOException;
import java.util.Map;
public class ChildProcessExample {
public static void main(String[] args) throws IOException, InterruptedException {
ProcessBuilder processBuilder = new ProcessBuilder();
Map environment = processBuilder.environment();
environment.put("MY_ENV_VARIABLE", "MY_ENV_VARIABLE_VALUE");
Process process = processBuilder.command("my-command").start();
int exitCode = process.waitFor();
System.out.println("Exit code: " + exitCode);
}
}
This example sets an environment variable for the child process started by ProcessBuilder
. It’s useful for scenarios where you need the environment variable to be available only within a specific subprocess.
4. Using Docker
Docker is another useful way to manage environment variables for Java applications, especially for integration tests. You can define environment variables in the Dockerfile as follows. This Docker file is based on amazoncorretto, a free image created by Amazon that includes the Java Development Kit (JDK).
FROM maven:3.9.8-amazoncorretto-8-al2023
WORKDIR /app
COPY . /app
ENV MY_DOCKER_ENV_VARIABLE=TRUE
ENTRYPOINT ["mvn", "test"]
In your Java code, you can use libraries like Testcontainers to run tests inside a Docker container:
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer;
class DockerEnvironmentTest {
@Test
void givenDockerEnvironment_whenGetEnvironmentVariable_thenReturnsCorrectValue() {
try (GenericContainer> container = new GenericContainer<>("my-docker-image")
.withEnv("MY_ENV_VARIABLE", "MY_ENV_VARIABLE_VALUE")) {
container.start();
// My custom tests
}
}
}
This approach ensures that your environment variables are isolated within the container, providing a controlled environment for testing and deployment.
Related content: Read our guide to testing environment
Managing Java Environment Variables with Configu
Configu is a configuration management platform comprised of two main components, the stand-alone Orchestrator, which is open source, and the Cloud, which is a SaaS solution:
Configu Orchestrator
As applications become more dynamic and distributed in microservices architectures, configurations are getting more fragmented. They are saved as raw text that is spread across multiple stores, databases, files, git repositories, and third-party tools (a typical company will have five to ten different stores).
The Configu Orchestrator, which is open-source software, is a powerful standalone tool designed to address this challenge by providing confConfigu is a configuration management platform comprised of two main components, the stand-alone Orchestrator, which is open source, and the Cloud, which is a SaaS solution:
Configu Cloud
Configu Cloud is the most innovative store purpose-built for configurations, including environment variables, secrets, and feature flags. It is built based on the Configu configuration-as-code (CaC) approach and can model configurations and wrap them with unique layers, providing collaboration capabilities, visibility into configuration workflows, and security and compliance standardization.
Unlike legacy tools, which treat configurations as unstructured data or key-value pairs, Configu is leading the way with a Configuration-as-Code approach. By modeling configurations, they are treated as first-class citizens in the developers’ code. This makes our solution more robust and reliable and also enables Configu to provide more capabilities, such as visualization, a testing framework, and security abilities.