CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-typesafe--config

Configuration library for JVM languages using HOCON files

Pending
Overview
Eval results
Files

resolution.mddocs/

Resolution and Substitution

Resolution and substitution provide dynamic configuration capabilities through variable substitution using ${path} syntax. The system supports environment variables, system properties, and custom resolvers with comprehensive fallback and override mechanisms.

Resolution Process

Basic Resolution

Resolve all substitutions in a configuration to create a fully resolved Config.

public Config resolve();
public Config resolve(ConfigResolveOptions options);
public boolean isResolved();

Usage Examples:

// Basic resolution (resolves all ${...} substitutions)
Config unresolved = ConfigFactory.parseString("""
    app {
        name = "MyApp"
        version = "1.0.0"
        database {
            url = "jdbc:postgresql://"${database.host}":"${database.port}"/"${database.name}
            host = ${?DB_HOST}
            port = ${?DB_PORT}
            name = ${?DB_NAME}
        }
    }
    """);

Config resolved = unresolved.resolve();

// Check if configuration is fully resolved
if (resolved.isResolved()) {
    String dbUrl = resolved.getString("app.database.url");
}

Resolution with External Source

Resolve substitutions using values from another configuration.

public Config resolveWith(Config source);
public Config resolveWith(Config source, ConfigResolveOptions options);

Usage Examples:

// Configuration with substitutions
Config template = ConfigFactory.parseString("""
    server {
        host = ${server.host}
        port = ${server.port}
        ssl = ${server.ssl.enabled}
    }
    """);

// Source configuration with actual values
Config values = ConfigFactory.parseString("""
    server {
        host = "production.example.com"
        port = 8443
        ssl.enabled = true
    }
    """);

// Resolve template using values
Config resolved = template.resolveWith(values);

Substitution Syntax

Basic Substitutions

Standard path-based substitutions reference other configuration values.

Syntax Examples:

# Reference another path
app.name = "MyApp"
app.display-name = ${app.name}

# Nested path references
database {
    host = "localhost"
    port = 5432
    url = "jdbc:postgresql://"${database.host}":"${database.port}"/mydb"
}

# Self-references and concatenation
base-path = "/api/v1"
endpoints {
    users = ${base-path}"/users"
    orders = ${base-path}"/orders"
}

Optional Substitutions

Optional substitutions use ${?path} syntax and don't fail if the path is missing.

Syntax Examples:

# Optional environment variable substitution
database {
    host = "localhost"
    host = ${?DATABASE_HOST}  # Override with env var if present
    
    port = 5432
    port = ${?DATABASE_PORT}
    
    # Optional with fallback chain
    url = ${?DATABASE_URL}
    url = "jdbc:postgresql://"${database.host}":"${database.port}"/mydb"
}

# Optional system property
jvm {
    max-heap = "512M"
    max-heap = ${?JAVA_MAX_HEAP}
}

Self-Substitution

Self-substitution allows a path to reference its own previous value.

Syntax Examples:

# Append to existing value
path = "/base"
path = ${path}"/extended"  # Results in "/base/extended"

# Prepend environment-specific prefix
config-file = "app.conf"
config-file = ${?ENV}"."${config-file}  # Could become "prod.app.conf"

Resolution Options

ConfigResolveOptions Class

Control resolution behavior with comprehensive options.

public final class ConfigResolveOptions {
    public static ConfigResolveOptions defaults();
    public static ConfigResolveOptions noSystem();
    public ConfigResolveOptions setUseSystemEnvironment(boolean value);
    public ConfigResolveOptions setAllowUnresolved(boolean value);
    public ConfigResolveOptions appendResolver(ConfigResolver resolver);
    public List<ConfigResolver> getResolvers();
}

Usage Examples:

// Default resolution (includes system environment and properties)
Config resolved = config.resolve();

// Resolution without system environment
Config resolved = config.resolve(ConfigResolveOptions.noSystem());

// Custom resolution options
ConfigResolveOptions options = ConfigResolveOptions.defaults()
    .setUseSystemEnvironment(false)  // Disable environment variables
    .setAllowUnresolved(true);       // Allow unresolved substitutions

Config partiallyResolved = config.resolve(options);

// Add custom resolver
ConfigResolver customResolver = new MyCustomResolver();
ConfigResolveOptions withCustom = ConfigResolveOptions.defaults()
    .appendResolver(customResolver);

Config resolved = config.resolve(withCustom);

System Integration

Automatic integration with system properties and environment variables.

Environment Variables:

# Environment variables are available automatically
database.host = ${?DATABASE_HOST}
database.port = ${?DATABASE_PORT}
app.debug = ${?DEBUG_MODE}

System Properties:

# System properties are available automatically  
jvm.max-heap = ${?java.max.heap}
user.home = ${user.home}
temp.dir = ${java.io.tmpdir}

Usage Examples:

// Set environment variables (in shell or process builder)
// export DATABASE_HOST=prod.db.example.com
// export DATABASE_PORT=5432

// Set system properties
System.setProperty("app.environment", "production");
System.setProperty("log.level", "INFO");

// Configuration automatically picks up system values
Config config = ConfigFactory.load();

Custom Resolvers

ConfigResolver Interface

Create custom substitution resolvers for specialized value sources.

public interface ConfigResolver {
    ConfigValue lookup(String path);
    ConfigResolver withFallback(ConfigResolver fallback);
}

Implementation Examples:

// Custom resolver for external service configuration
public class ServiceConfigResolver implements ConfigResolver {
    private final ConfigService configService;
    
    public ServiceConfigResolver(ConfigService service) {
        this.configService = service;
    }
    
    @Override
    public ConfigValue lookup(String path) {
        try {
            String value = configService.getValue(path);
            if (value != null) {
                return ConfigValueFactory.fromAnyRef(value, "service:" + path);
            }
        } catch (Exception e) {
            // Log error but don't fail resolution
        }
        return null; // Path not found in this resolver
    }
    
    @Override
    public ConfigResolver withFallback(ConfigResolver fallback) {
        return new FallbackConfigResolver(this, fallback);
    }
}

// Usage
ConfigResolver serviceResolver = new ServiceConfigResolver(myConfigService);
ConfigResolveOptions options = ConfigResolveOptions.defaults()
    .appendResolver(serviceResolver);

Config resolved = config.resolve(options);

Resolver Chaining

Chain multiple resolvers with fallback behavior.

// Chain resolvers in priority order
ConfigResolver primary = new DatabaseConfigResolver();
ConfigResolver secondary = new FileConfigResolver();
ConfigResolver tertiary = new DefaultConfigResolver();

ConfigResolver chain = primary
    .withFallback(secondary)
    .withFallback(tertiary);

ConfigResolveOptions options = ConfigResolveOptions.defaults()
    .appendResolver(chain);

Error Handling

Resolution Exceptions

Handle various resolution error conditions.

public static class ConfigException.UnresolvedSubstitution extends ConfigException {
    public String path();
    public String detail();
}

public static class ConfigException.NotResolved extends ConfigException {
    public String getMessage();
}

Error Handling Examples:

try {
    Config resolved = config.resolve();
} catch (ConfigException.UnresolvedSubstitution e) {
    // Handle unresolved substitution
    String problematicPath = e.path();
    String details = e.detail();
    System.err.println("Cannot resolve ${" + problematicPath + "}: " + details);
}

// Check resolution status before accessing values
if (!config.isResolved()) {
    try {
        config.resolve();
    } catch (ConfigException.UnresolvedSubstitution e) {
        // Handle or allow partial resolution
        Config partial = config.resolve(
            ConfigResolveOptions.defaults().setAllowUnresolved(true)
        );
    }
}

Advanced Resolution Patterns

Conditional Resolution

Use substitutions for conditional configuration.

# Environment-specific configuration
environment = "development"
environment = ${?APP_ENVIRONMENT}

# Conditional database settings
database = {
    development {
        host = "localhost"
        port = 5432
    }
    production {
        host = "prod.db.example.com"
        port = 5432
    }
}

# Select configuration based on environment
current-db = ${database.${environment}}

Template Resolution

Create configuration templates with substitution placeholders.

# Template configuration
service-template = {
    host = ${service.host}
    port = ${service.port}
    health-check = "http://"${service.host}":"${service.port}"/health"
    metrics = "http://"${service.host}":"${service.port}"/metrics"
}

# Specific service configurations
services {
    user-service = ${service-template} {
        service.host = "user.service.internal"
        service.port = 8080
    }
    
    order-service = ${service-template} {
        service.host = "order.service.internal"  
        service.port = 8081
    }
}

Cross-Configuration Resolution

Resolve substitutions across multiple configuration sources.

// Base configuration with substitutions
Config baseConfig = ConfigFactory.parseString("""
    app {
        name = ${app.metadata.name}
        version = ${app.metadata.version}
        database.url = ${database.connection.url}
    }
    """);

// Metadata configuration
Config metadata = ConfigFactory.parseString("""
    app.metadata {
        name = "MyApplication"
        version = "2.1.0"
    }
    """);

// Database configuration  
Config dbConfig = ConfigFactory.parseString("""
    database.connection {
        url = "jdbc:postgresql://localhost:5432/myapp"
    }
    """);

// Combine and resolve
Config combined = baseConfig
    .withFallback(metadata)
    .withFallback(dbConfig)
    .resolve();

Best Practices

  1. Use optional substitutions: Prefer ${?path} for environment variables and optional settings
  2. Provide fallback values: Always include default values for optional substitutions
  3. Validate resolution: Check isResolved() before using configuration in production
  4. Handle resolution errors: Catch and handle ConfigException.UnresolvedSubstitution appropriately
  5. Order resolver priority: Place most specific resolvers first in the chain
  6. Cache resolved configs: Resolution is expensive, cache the results when possible
  7. Use meaningful origins: Provide descriptive origin information in custom resolvers
  8. Test substitution scenarios: Verify behavior with missing, null, and circular references

Install with Tessl CLI

npx tessl i tessl/maven-com-typesafe--config

docs

access.md

exceptions.md

index.md

loading.md

options.md

resolution.md

values.md

tile.json