intermediatejava
Environment Utility
Centralized helper for reading environment variables, system properties, and profile-aware configuration with type-safe access.
Published October 17, 2025
Updated October 12, 2025
Configurationconfigurationenvutilityresilience
Environment Utility
A thread-safe helper that unifies access to environment variables, system properties, and environment-specific overrides. It keeps configuration lookups consistent, type-safe, and cache-friendly across any Java application.
Highlights
- Unified lookups prefer environment variables, then system properties, and support explicit defaults.
- Built-in converters cover numeric primitives, booleans, and concise duration literals (
5s,2m,1h). - Environment helpers expose the active profile (
dev,staging,prod, etc.) withisDev,isProd, andisTest. - Snapshot utilities provide full and sanitised maps for diagnostics without leaking secrets.
- Lock-free caching speeds up repeated reads and can be reset when configuration reloads.
Code
import java.time.Duration;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public final class EnvironmentUtil {
private static final ConcurrentMap<String, Optional<String>> CACHE = new ConcurrentHashMap<>();
private static final Set<String> SENSITIVE = Set.of(
"PASSWORD",
"SECRET",
"TOKEN",
"KEY",
"CREDENTIAL",
"PRIVATE"
);
private static final String[] PROFILE_KEYS = {
"SPRING_PROFILES_ACTIVE",
"APP_ENV",
"ENV",
"NODE_ENV"
};
private EnvironmentUtil() {
// Utility class
}
public static String get(String key) {
return resolve(key).orElse(null);
}
public static String get(String key, String defaultValue) {
return resolve(key).orElse(defaultValue);
}
public static Optional<String> getOptional(String key) {
return resolve(key);
}
public static String get(String key, String profile, String defaultValue) {
Objects.requireNonNull(profile, "profile");
String candidate = profileAwareKey(key, profile);
return resolve(candidate).orElseGet(() -> get(key, defaultValue));
}
public static String require(String key) {
return resolve(key).orElseThrow(() ->
new IllegalStateException("Missing mandatory configuration: " + key)
);
}
public static int getInt(String key, int defaultValue) {
String value = get(key);
if (value == null || value.isBlank()) {
return defaultValue;
}
try {
return Integer.parseInt(value.trim());
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("Invalid int value for key " + key + ": " + value, ex);
}
}
public static long getLong(String key, long defaultValue) {
String value = get(key);
if (value == null || value.isBlank()) {
return defaultValue;
}
try {
return Long.parseLong(value.trim());
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("Invalid long value for key " + key + ": " + value, ex);
}
}
public static double getDouble(String key, double defaultValue) {
String value = get(key);
if (value == null || value.isBlank()) {
return defaultValue;
}
try {
return Double.parseDouble(value.trim());
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("Invalid double value for key " + key + ": " + value, ex);
}
}
public static boolean getBoolean(String key, boolean defaultValue) {
String value = get(key);
if (value == null || value.isBlank()) {
return defaultValue;
}
String normalized = value.trim().toLowerCase(Locale.ROOT);
if ("true".equals(normalized) || "1".equals(normalized) || "yes".equals(normalized)) {
return true;
}
if ("false".equals(normalized) || "0".equals(normalized) || "no".equals(normalized)) {
return false;
}
throw new IllegalArgumentException("Invalid boolean value for key " + key + ": " + value);
}
public static Duration getDuration(String key, Duration defaultValue) {
String value = get(key);
if (value == null || value.isBlank()) {
return defaultValue;
}
return parseDuration(value);
}
public static String getActiveEnvironment() {
for (String key : PROFILE_KEYS) {
String value = get(key);
if (value != null && !value.isBlank()) {
return value.trim();
}
}
return "default";
}
public static boolean isDev() {
return isEnv("dev", "development", "local");
}
public static boolean isProd() {
return isEnv("prod", "production");
}
public static boolean isTest() {
return isEnv("test", "qa");
}
public static boolean isEnv(String... candidates) {
String active = getActiveEnvironment();
if (active == null) {
return false;
}
for (String candidate : candidates) {
if (candidate != null && active.equalsIgnoreCase(candidate)) {
return true;
}
}
return false;
}
public static Map<String, String> snapshot() {
Map<String, String> snapshot = new LinkedHashMap<>();
System.getenv().forEach(snapshot::putIfAbsent);
System.getProperties().forEach((key, value) ->
snapshot.put(String.valueOf(key), String.valueOf(value))
);
return Collections.unmodifiableMap(snapshot);
}
public static Map<String, String> snapshotSanitised() {
Map<String, String> redacted = new LinkedHashMap<>();
snapshot().forEach((key, value) ->
redacted.put(key, isSensitiveKey(key) ? "***" : value)
);
return Collections.unmodifiableMap(redacted);
}
public static void clearCache() {
CACHE.clear();
}
private static Optional<String> resolve(String key) {
Objects.requireNonNull(key, "key");
String trimmed = key.trim();
if (trimmed.isEmpty()) {
throw new IllegalArgumentException("Key must not be blank.");
}
return CACHE.computeIfAbsent(trimmed, EnvironmentUtil::loadValue);
}
private static Optional<String> loadValue(String key) {
String env = System.getenv(key);
if (env != null && !env.isBlank()) {
return Optional.of(env);
}
String property = System.getProperty(key);
if (property != null && !property.isBlank()) {
return Optional.of(property);
}
return Optional.empty();
}
private static Duration parseDuration(String raw) {
String value = raw.trim().toLowerCase(Locale.ROOT);
try {
if (value.endsWith("ms")) {
return Duration.ofMillis(Long.parseLong(value.substring(0, value.length() - 2).trim()));
}
if (value.endsWith("s")) {
return Duration.ofSeconds(Long.parseLong(value.substring(0, value.length() - 1).trim()));
}
if (value.endsWith("m")) {
return Duration.ofMinutes(Long.parseLong(value.substring(0, value.length() - 1).trim()));
}
if (value.endsWith("h")) {
return Duration.ofHours(Long.parseLong(value.substring(0, value.length() - 1).trim()));
}
if (value.endsWith("d")) {
return Duration.ofDays(Long.parseLong(value.substring(0, value.length() - 1).trim()));
}
if (value.matches("\\d+")) {
return Duration.ofSeconds(Long.parseLong(value));
}
return Duration.parse(raw);
} catch (Exception ex) {
throw new IllegalArgumentException("Invalid duration value: " + raw, ex);
}
}
private static String profileAwareKey(String key, String profile) {
return key + "_" + profile.trim().toUpperCase(Locale.ROOT);
}
private static boolean isSensitiveKey(String key) {
String upper = key.toUpperCase(Locale.ROOT);
for (String token : SENSITIVE) {
if (upper.contains(token)) {
return true;
}
}
return false;
}
}
Usage
import java.time.Duration;
import java.util.Map;
import java.util.logging.Logger;
public final class AppConfig {
private static final Logger LOG = Logger.getLogger(AppConfig.class.getName());
public static final int PORT = EnvironmentUtil.getInt("APP_PORT", 8080);
public static final Duration REQUEST_TIMEOUT = EnvironmentUtil.getDuration("HTTP_TIMEOUT", Duration.ofSeconds(5));
public static final String DATABASE_URL = EnvironmentUtil.require("DATABASE_URL");
static {
LOG.info(() -> "Running in " + EnvironmentUtil.getActiveEnvironment() + " mode");
Map<String, String> envSnapshot = EnvironmentUtil.snapshotSanitised();
LOG.fine(() -> "Sanitised environment: " + envSnapshot);
}
private AppConfig() {
}
}
if (EnvironmentUtil.isProd()) {
enableStrictSecurity();
}
String regionalEndpoint = EnvironmentUtil.get("API_ENDPOINT", "eu", "https://api.local");
💡 Integrations that supply their own configuration (Spring, Micronaut, Quarkus, etc.) can still delegate to
EnvironmentUtiland callclearCache()when refreshing the application context.