0
# Resolution and Substitution
1
2
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.
3
4
## Resolution Process
5
6
### Basic Resolution
7
8
Resolve all substitutions in a configuration to create a fully resolved Config.
9
10
```java { .api }
11
public Config resolve();
12
public Config resolve(ConfigResolveOptions options);
13
public boolean isResolved();
14
```
15
16
**Usage Examples:**
17
18
```java
19
// Basic resolution (resolves all ${...} substitutions)
20
Config unresolved = ConfigFactory.parseString("""
21
app {
22
name = "MyApp"
23
version = "1.0.0"
24
database {
25
url = "jdbc:postgresql://"${database.host}":"${database.port}"/"${database.name}
26
host = ${?DB_HOST}
27
port = ${?DB_PORT}
28
name = ${?DB_NAME}
29
}
30
}
31
""");
32
33
Config resolved = unresolved.resolve();
34
35
// Check if configuration is fully resolved
36
if (resolved.isResolved()) {
37
String dbUrl = resolved.getString("app.database.url");
38
}
39
```
40
41
### Resolution with External Source
42
43
Resolve substitutions using values from another configuration.
44
45
```java { .api }
46
public Config resolveWith(Config source);
47
public Config resolveWith(Config source, ConfigResolveOptions options);
48
```
49
50
**Usage Examples:**
51
52
```java
53
// Configuration with substitutions
54
Config template = ConfigFactory.parseString("""
55
server {
56
host = ${server.host}
57
port = ${server.port}
58
ssl = ${server.ssl.enabled}
59
}
60
""");
61
62
// Source configuration with actual values
63
Config values = ConfigFactory.parseString("""
64
server {
65
host = "production.example.com"
66
port = 8443
67
ssl.enabled = true
68
}
69
""");
70
71
// Resolve template using values
72
Config resolved = template.resolveWith(values);
73
```
74
75
## Substitution Syntax
76
77
### Basic Substitutions
78
79
Standard path-based substitutions reference other configuration values.
80
81
**Syntax Examples:**
82
83
```hocon
84
# Reference another path
85
app.name = "MyApp"
86
app.display-name = ${app.name}
87
88
# Nested path references
89
database {
90
host = "localhost"
91
port = 5432
92
url = "jdbc:postgresql://"${database.host}":"${database.port}"/mydb"
93
}
94
95
# Self-references and concatenation
96
base-path = "/api/v1"
97
endpoints {
98
users = ${base-path}"/users"
99
orders = ${base-path}"/orders"
100
}
101
```
102
103
### Optional Substitutions
104
105
Optional substitutions use `${?path}` syntax and don't fail if the path is missing.
106
107
**Syntax Examples:**
108
109
```hocon
110
# Optional environment variable substitution
111
database {
112
host = "localhost"
113
host = ${?DATABASE_HOST} # Override with env var if present
114
115
port = 5432
116
port = ${?DATABASE_PORT}
117
118
# Optional with fallback chain
119
url = ${?DATABASE_URL}
120
url = "jdbc:postgresql://"${database.host}":"${database.port}"/mydb"
121
}
122
123
# Optional system property
124
jvm {
125
max-heap = "512M"
126
max-heap = ${?JAVA_MAX_HEAP}
127
}
128
```
129
130
### Self-Substitution
131
132
Self-substitution allows a path to reference its own previous value.
133
134
**Syntax Examples:**
135
136
```hocon
137
# Append to existing value
138
path = "/base"
139
path = ${path}"/extended" # Results in "/base/extended"
140
141
# Prepend environment-specific prefix
142
config-file = "app.conf"
143
config-file = ${?ENV}"."${config-file} # Could become "prod.app.conf"
144
```
145
146
## Resolution Options
147
148
### ConfigResolveOptions Class
149
150
Control resolution behavior with comprehensive options.
151
152
```java { .api }
153
public final class ConfigResolveOptions {
154
public static ConfigResolveOptions defaults();
155
public static ConfigResolveOptions noSystem();
156
public ConfigResolveOptions setUseSystemEnvironment(boolean value);
157
public ConfigResolveOptions setAllowUnresolved(boolean value);
158
public ConfigResolveOptions appendResolver(ConfigResolver resolver);
159
public List<ConfigResolver> getResolvers();
160
}
161
```
162
163
**Usage Examples:**
164
165
```java
166
// Default resolution (includes system environment and properties)
167
Config resolved = config.resolve();
168
169
// Resolution without system environment
170
Config resolved = config.resolve(ConfigResolveOptions.noSystem());
171
172
// Custom resolution options
173
ConfigResolveOptions options = ConfigResolveOptions.defaults()
174
.setUseSystemEnvironment(false) // Disable environment variables
175
.setAllowUnresolved(true); // Allow unresolved substitutions
176
177
Config partiallyResolved = config.resolve(options);
178
179
// Add custom resolver
180
ConfigResolver customResolver = new MyCustomResolver();
181
ConfigResolveOptions withCustom = ConfigResolveOptions.defaults()
182
.appendResolver(customResolver);
183
184
Config resolved = config.resolve(withCustom);
185
```
186
187
### System Integration
188
189
Automatic integration with system properties and environment variables.
190
191
**Environment Variables:**
192
```hocon
193
# Environment variables are available automatically
194
database.host = ${?DATABASE_HOST}
195
database.port = ${?DATABASE_PORT}
196
app.debug = ${?DEBUG_MODE}
197
```
198
199
**System Properties:**
200
```hocon
201
# System properties are available automatically
202
jvm.max-heap = ${?java.max.heap}
203
user.home = ${user.home}
204
temp.dir = ${java.io.tmpdir}
205
```
206
207
**Usage Examples:**
208
209
```java
210
// Set environment variables (in shell or process builder)
211
// export DATABASE_HOST=prod.db.example.com
212
// export DATABASE_PORT=5432
213
214
// Set system properties
215
System.setProperty("app.environment", "production");
216
System.setProperty("log.level", "INFO");
217
218
// Configuration automatically picks up system values
219
Config config = ConfigFactory.load();
220
```
221
222
## Custom Resolvers
223
224
### ConfigResolver Interface
225
226
Create custom substitution resolvers for specialized value sources.
227
228
```java { .api }
229
public interface ConfigResolver {
230
ConfigValue lookup(String path);
231
ConfigResolver withFallback(ConfigResolver fallback);
232
}
233
```
234
235
**Implementation Examples:**
236
237
```java
238
// Custom resolver for external service configuration
239
public class ServiceConfigResolver implements ConfigResolver {
240
private final ConfigService configService;
241
242
public ServiceConfigResolver(ConfigService service) {
243
this.configService = service;
244
}
245
246
@Override
247
public ConfigValue lookup(String path) {
248
try {
249
String value = configService.getValue(path);
250
if (value != null) {
251
return ConfigValueFactory.fromAnyRef(value, "service:" + path);
252
}
253
} catch (Exception e) {
254
// Log error but don't fail resolution
255
}
256
return null; // Path not found in this resolver
257
}
258
259
@Override
260
public ConfigResolver withFallback(ConfigResolver fallback) {
261
return new FallbackConfigResolver(this, fallback);
262
}
263
}
264
265
// Usage
266
ConfigResolver serviceResolver = new ServiceConfigResolver(myConfigService);
267
ConfigResolveOptions options = ConfigResolveOptions.defaults()
268
.appendResolver(serviceResolver);
269
270
Config resolved = config.resolve(options);
271
```
272
273
### Resolver Chaining
274
275
Chain multiple resolvers with fallback behavior.
276
277
```java
278
// Chain resolvers in priority order
279
ConfigResolver primary = new DatabaseConfigResolver();
280
ConfigResolver secondary = new FileConfigResolver();
281
ConfigResolver tertiary = new DefaultConfigResolver();
282
283
ConfigResolver chain = primary
284
.withFallback(secondary)
285
.withFallback(tertiary);
286
287
ConfigResolveOptions options = ConfigResolveOptions.defaults()
288
.appendResolver(chain);
289
```
290
291
## Error Handling
292
293
### Resolution Exceptions
294
295
Handle various resolution error conditions.
296
297
```java { .api }
298
public static class ConfigException.UnresolvedSubstitution extends ConfigException {
299
public String path();
300
public String detail();
301
}
302
303
public static class ConfigException.NotResolved extends ConfigException {
304
public String getMessage();
305
}
306
```
307
308
**Error Handling Examples:**
309
310
```java
311
try {
312
Config resolved = config.resolve();
313
} catch (ConfigException.UnresolvedSubstitution e) {
314
// Handle unresolved substitution
315
String problematicPath = e.path();
316
String details = e.detail();
317
System.err.println("Cannot resolve ${" + problematicPath + "}: " + details);
318
}
319
320
// Check resolution status before accessing values
321
if (!config.isResolved()) {
322
try {
323
config.resolve();
324
} catch (ConfigException.UnresolvedSubstitution e) {
325
// Handle or allow partial resolution
326
Config partial = config.resolve(
327
ConfigResolveOptions.defaults().setAllowUnresolved(true)
328
);
329
}
330
}
331
```
332
333
## Advanced Resolution Patterns
334
335
### Conditional Resolution
336
337
Use substitutions for conditional configuration.
338
339
```hocon
340
# Environment-specific configuration
341
environment = "development"
342
environment = ${?APP_ENVIRONMENT}
343
344
# Conditional database settings
345
database = {
346
development {
347
host = "localhost"
348
port = 5432
349
}
350
production {
351
host = "prod.db.example.com"
352
port = 5432
353
}
354
}
355
356
# Select configuration based on environment
357
current-db = ${database.${environment}}
358
```
359
360
### Template Resolution
361
362
Create configuration templates with substitution placeholders.
363
364
```hocon
365
# Template configuration
366
service-template = {
367
host = ${service.host}
368
port = ${service.port}
369
health-check = "http://"${service.host}":"${service.port}"/health"
370
metrics = "http://"${service.host}":"${service.port}"/metrics"
371
}
372
373
# Specific service configurations
374
services {
375
user-service = ${service-template} {
376
service.host = "user.service.internal"
377
service.port = 8080
378
}
379
380
order-service = ${service-template} {
381
service.host = "order.service.internal"
382
service.port = 8081
383
}
384
}
385
```
386
387
### Cross-Configuration Resolution
388
389
Resolve substitutions across multiple configuration sources.
390
391
```java
392
// Base configuration with substitutions
393
Config baseConfig = ConfigFactory.parseString("""
394
app {
395
name = ${app.metadata.name}
396
version = ${app.metadata.version}
397
database.url = ${database.connection.url}
398
}
399
""");
400
401
// Metadata configuration
402
Config metadata = ConfigFactory.parseString("""
403
app.metadata {
404
name = "MyApplication"
405
version = "2.1.0"
406
}
407
""");
408
409
// Database configuration
410
Config dbConfig = ConfigFactory.parseString("""
411
database.connection {
412
url = "jdbc:postgresql://localhost:5432/myapp"
413
}
414
""");
415
416
// Combine and resolve
417
Config combined = baseConfig
418
.withFallback(metadata)
419
.withFallback(dbConfig)
420
.resolve();
421
```
422
423
## Best Practices
424
425
1. **Use optional substitutions**: Prefer `${?path}` for environment variables and optional settings
426
2. **Provide fallback values**: Always include default values for optional substitutions
427
3. **Validate resolution**: Check `isResolved()` before using configuration in production
428
4. **Handle resolution errors**: Catch and handle `ConfigException.UnresolvedSubstitution` appropriately
429
5. **Order resolver priority**: Place most specific resolvers first in the chain
430
6. **Cache resolved configs**: Resolution is expensive, cache the results when possible
431
7. **Use meaningful origins**: Provide descriptive origin information in custom resolvers
432
8. **Test substitution scenarios**: Verify behavior with missing, null, and circular references