Snippets/java/Environment Utility
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
Configuration
configurationenvutilityresilience

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.) with isDev, isProd, and isTest.
  • 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 EnvironmentUtil and call clearCache() when refreshing the application context.