javabeginner

Properties and Config File Loader

Load configuration from .properties files, system env, and defaults with a type-safe config class.

java
import java.io.*;
import java.nio.file.*;
import java.util.*;

public class AppConfig {
    private final Properties props;

    private AppConfig(Properties props) {
        this.props = props;
    }

    // Builder with layered sources
    static class Builder {
        private final Properties merged = new Properties();

        // Load from classpath resource
        Builder fromClasspath(String resource) {
            try (var is = getClass().getClassLoader().getResourceAsStream(resource)) {
                if (is != null) merged.load(is);
            } catch (IOException e) {
                System.err.println("Warn: " + resource + " not found");
            }
            return this;
        }

        // Load from file path
        Builder fromFile(String path) {
            try (var reader = Files.newBufferedReader(Path.of(path))) {
                merged.load(reader);
            } catch (IOException e) {
                System.err.println("Warn: " + path + " not found");
            }
            return this;
        }

        // Override with environment variables (APP_DB_HOST → db.host)
        Builder fromEnv(String prefix) {
            System.getenv().forEach((key, value) -> {
                if (key.startsWith(prefix)) {
                    String propKey = key.substring(prefix.length())
                        .toLowerCase().replace('_', '.');
                    merged.setProperty(propKey, value);
                }
            });
            return this;
        }

        // Override with system properties
        Builder fromSystemProps() {
            System.getProperties().forEach((k, v) ->
                merged.setProperty(k.toString(), v.toString()));
            return this;
        }

        AppConfig build() { return new AppConfig(merged); }
    }

    // Type-safe getters
    public String get(String key) {
        return Optional.ofNullable(props.getProperty(key))
            .orElseThrow(() -> new NoSuchElementException("Missing: " + key));
    }

    public String get(String key, String defaultValue) {
        return props.getProperty(key, defaultValue);
    }

    public int getInt(String key, int defaultValue) {
        String val = props.getProperty(key);
        return val != null ? Integer.parseInt(val) : defaultValue;
    }

    public boolean getBool(String key, boolean defaultValue) {
        String val = props.getProperty(key);
        return val != null ? Boolean.parseBoolean(val) : defaultValue;
    }

    public List<String> getList(String key) {
        String val = props.getProperty(key, "");
        return val.isEmpty() ? List.of() :
            Arrays.stream(val.split(",")).map(String::trim).toList();
    }

    public void dump() {
        props.forEach((k, v) -> System.out.printf("%s = %s%n", k, v));
    }

    // Usage
    public static void main(String[] args) {
        AppConfig config = new Builder()
            .fromClasspath("application.properties") // base
            .fromFile("config/local.properties")      // override
            .fromEnv("APP_")                           // env override
            .fromSystemProps()                         // system override
            .build();

        System.out.println("DB Host: " + config.get("db.host", "localhost"));
        System.out.println("Port: " + config.getInt("server.port", 8080));
        System.out.println("Debug: " + config.getBool("debug", false));
        System.out.println("Tags: " + config.getList("app.tags"));
    }
}

Use Cases

  • Application configuration with environment overrides
  • 12-factor app configuration management
  • Type-safe property access with defaults

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.