Skip to content

Advanced Configuration Handling

This page collects higher-level patterns for controlling how mConfig resolves, validates, and writes configuration data.

1. Write with scope control

Writes are scoped and follow a "least surprise" policy:

Configuration cfg = factory.getConfiguration("network");

cfg.put("server/port", 8081, ConfigScope.USER);

2. Control file format order

Reading and writing can use different format orders:

ConfigFactoryBuilder builder = ConfigFactoryBuilder.create("ACME", "ourApp")
        .setFeature(ConfigFeature.FILE_FORMAT_READING_PRIORITIES,
            List.of("TOML", "YAML", "JSON", "properties"))
        .setFeature(ConfigFeature.FILE_FORMAT_WRITING_PRIORITIES,
            List.of("TOML", "YAML", "JSON"));

3. Use strict schemes

To reject unknown keys, turn on strict mode:

ConfigFactoryBuilder builder = ConfigFactoryBuilder.create("ACME", "ourApp")
        .setFeature(ConfigFeature.SCHEME_STRICT_MODE, true);

4. Missing values behavior

If you want scheme defaults instead of exceptions:

ConfigFactoryBuilder builder = ConfigFactoryBuilder.create("ACME", "ourApp")
        .setFeature(ConfigFeature.EXCEPTION_ON_MISSING_ENTRY, false)
        .setFeature(ConfigFeature.DEFAULT_ON_MISSING_ENTRY, true);

5. Subscribe to updates

React when values change at runtime:

Configuration cfg = factory.getConfiguration("network");

cfg.subscribeToUpdates(location -> {
    System.out.println("Config changed in scope: " + location.getScope());
});

NB: The changes are automatically propagated to the mConfig Configurations anyhow. If you take the values from the Configurations directly, they will be up-to-date. Above subscription is to support triggering actions when the configuration changes, e.g. thread restarts.

6. Control scope fallbacks

By default, mConfig falls back to less specific scopes when a value is missing. You can change that:

try (ConfigFactory factory = ConfigFactoryBuilder.create("ACME", "ourApp")
        .setFeature(ConfigFeature.FALLBACKS_ACROSS_SCOPES, false)
        .build())
    {
    Configuration cfg = factory.getConfiguration("network");
    // Only the requested scope (plus PRODUCT defaults) is considered.
    }

7. Prioritize storage types

If multiple storage backends are available, change their priority within a scope:

try (ConfigFactory factory = ConfigFactoryBuilder.create("ACME", "ourApp")
        .setFeature(ConfigFeature.STORAGE_TYPE_PRIORITIES,
            List.of("files", "registry", "JAR"))
        .build())
    {
    // Files override registry, registry overrides JAR defaults.
    }

8. Use secrets providers

If you have secrets in schemes, add a secrets provider to resolve them:

ConfigFactoryBuilder builder = ConfigFactoryBuilder.create("ACME", "ourApp")
        .setFeature(ConfigFeature.SECRETS_PROVIDER_ID, "my-secrets")
        .setSecretsProviderConfig(Map.of("endpoint", "https://vault.example"));

Related docs: - Config Features - Priorities and Hierarchies - Writing Configurations