env.dev

Environment Variables in Java: System.getenv & Spring Boot

Read env vars in Java with System.getenv() (immutable from inside the JVM) and bind them to typed config via Spring Boot relaxed binding. Plus dotenv-java for non-Spring apps.

Last updated:

Java reads environment variables through System.getenv(), which returns an unmodifiable Map<String, String>. Once the JVM starts, that map is read-only — there is no portable API to set or remove a variable from inside the process. That immutability is the central gotcha for developers coming from Node.js or Python: you cannot "patch" the environment at runtime, so configuration must be sourced before the JVM starts or layered through a higher-level abstraction. In modern Spring Boot apps, that abstraction is IConfiguration — sorry, wrong runtime. In Spring Boot it is the Environment / @Value machinery, which automatically maps SPRING_DATASOURCE_URL to spring.datasource.url via relaxed binding. This guide covers the primitive APIs, the Spring Boot mapping rules, and the dotenv-java escape hatch for non-Spring projects.

How do you read an environment variable in Java?

Two methods on java.lang.System matter: getenv(String) for a single variable and getenv() for the whole map. Both return null for unset keys — guard with Optional or Map.getOrDefault to avoid NullPointerException.

java
// Single value — returns null if unset
String dbUrl = System.getenv("DATABASE_URL");

// With a default
String port = System.getenv().getOrDefault("PORT", "3000");

// Optional-style guard
String apiKey = Optional.ofNullable(System.getenv("API_KEY"))
    .orElseThrow(() -> new IllegalStateException("API_KEY not set"));

// Whole map — unmodifiable
Map<String, String> all = System.getenv();
all.forEach((k, v) -> System.out.println(k + "=" + v));

Environment variables vs JVM system properties

Java has a second, separate namespace: system properties, accessed via System.getProperty and set with the -D flag. Many libraries (especially older ones) read system properties rather than environment variables — Java logging, the SSL truststore, and almost everything in the java. namespace.

bash
# Environment variable
export DATABASE_URL=postgres://localhost/mydb
java -jar app.jar

# System property — same value reached via System.getProperty("database.url")
java -Ddatabase.url=postgres://localhost/mydb -jar app.jar

# Java built-in system properties worth knowing
java.version          # JVM version
java.home             # JRE installation dir
user.home             # User home directory
file.separator        # / or \
line.separator        # \n or \r\n
os.name               # "Linux", "Mac OS X", "Windows 11"

When debugging configuration issues, check both: a missing value might be in the wrong namespace, not actually unset.

How does Spring Boot bind environment variables?

Spring Boot's relaxed binding automatically maps environment variables to property names. Underscores become dots, uppercase becomes lowercase, and the resulting property is available everywhere Spring's configuration system reaches — @Value, @ConfigurationProperties, application.yml placeholders, and the Environment bean.

bash
# Environment variable           Spring property
SPRING_DATASOURCE_URL          → spring.datasource.url
SPRING_DATASOURCE_USERNAME     → spring.datasource.username
SERVER_PORT                    → server.port
LOGGING_LEVEL_ROOT             → logging.level.root
MYAPP_FEATURE_NEW_UI_ENABLED   → myapp.feature.new-ui.enabled
java
// Inject via @Value
@Value("${spring.datasource.url}") String dbUrl;

// Or as a typed bean
@ConfigurationProperties(prefix = "myapp")
public record AppConfig(
    String name,
    int    maxConnections,
    Duration timeout
) {}

# In application.yml — defaults baked in, env vars override
myapp:
  name: my-app
  max-connections: 100
  timeout: 30s

Spring's precedence order (loosely): command-line args → JNDI → JVM system properties → OS environment variables → application-{profile}.yml application.yml → defaults. Higher sources override lower ones.

How do you load a .env file in Java?

Spring Boot doesn't read .env files natively. The community standard for non-Spring apps is dotenv-java, which exposes its own API rather than mutating System.getenv() (since the latter is read-only):

java
// Maven: io.github.cdimascio:dotenv-java:3.0.0
import io.github.cdimascio.dotenv.Dotenv;

Dotenv dotenv = Dotenv.configure()
    .directory("./config")
    .ignoreIfMalformed()
    .ignoreIfMissing()
    .load();

String dbUrl = dotenv.get("DATABASE_URL");
String port  = dotenv.get("PORT", "3000");

For Spring Boot, use spring-dotenv as a build-time plugin or a custom PropertySource that reads .env at startup.

Practical patterns and gotchas

  • The environment is immutable from inside the JVM. There is no standard System.setenv. If you genuinely need runtime mutation for a test, hack it with reflection (Linux/Mac) or JNI — but most of the time you actually want a config object you control.
  • JAVA_OPTS and JAVA_TOOL_OPTIONS let containers pass JVM flags via the environment without changing the entrypoint. JAVA_TOOL_OPTIONS is honoured by every JVM tool, including jstack and jcmd.
  • Spring Boot relaxed binding handles kebab-case: MYAPP_FEATURE_NEW_UI binds to myapp.feature.new-ui. Use @ConfigurationProperties with kebab-case property names; Spring translates automatically.
  • Container env-only deployment: in modern container orchestrators, JVM apps usually skip config files entirely and rely on environment variables injected by Kubernetes ConfigMaps / Secrets, AWS Parameter Store, or Azure Key Vault. See the env-vars tips guide for Kubernetes patterns.
  • Validate your .env file before committing with the .env validator.

For Java's static final and enum constants, see constants in Java.

Was this helpful?

Read next

Environment Variables in Rust: std::env, dotenvy & envy

Read env vars in Rust with std::env::var, load .env files via dotenvy, and deserialize typed config with envy. Plus the Rust 2024 unsafe-set_var change.

Continue →

Frequently Asked Questions

Can I set an environment variable from inside a running JVM?

No. System.getenv() returns an unmodifiable map, and there is no portable API to mutate it. You can hack it via reflection on Linux/macOS, but the supported pattern is to source variables before the JVM starts or layer them through a higher-level abstraction like Spring's Environment.

What is the difference between System.getenv and System.getProperty?

They are entirely separate namespaces. System.getenv reads OS environment variables; System.getProperty reads JVM system properties set with -D flags. Many JVM and library configuration values use system properties, not env vars.

How does Spring Boot relaxed binding map env vars to properties?

Underscores become dots, uppercase becomes lowercase. SPRING_DATASOURCE_URL maps to spring.datasource.url, MYAPP_FEATURE_NEW_UI maps to myapp.feature.new-ui. The mapping reaches every Spring config consumer — @Value, @ConfigurationProperties, application.yml placeholders.

How do I load a .env file in Java?

For non-Spring apps, use dotenv-java (cdimascio/dotenv-java). It exposes its own API rather than mutating System.getenv (which is read-only). For Spring Boot, use spring-dotenv as a build plugin or a custom PropertySource.

Stay up to date

Get notified about new guides, tools, and cheatsheets.