Skip to content

Configuration API

This is the API reference for the configuration subsystem: the type you get back from the SDK facade, its read methods, the typed section accessors, template resolution, and the ConfigurationChangeListener interface. For a task-oriented walkthrough (choosing a source, the document layout, hot reload) see the Configuration guide; for the exhaustive field list see the configuration schema reference.

How you obtain config from the top-level SDK object is the first cross-language divergence. Java and Python hand you a rich manager object with getters; Rust and TypeScript hand you an immutable Config snapshot that is replaced atomically on hot reload.

Language Accessor Returns
Java gg.getConfigManager() ConfigManager (mutable manager, many getters)
Python gg.get_config_manager() ConfigManager
Rust gg.config() Arc<Config> (immutable snapshot)
TypeScript gg.config() Config (immutable snapshot)

The core read surface — global config, instance ids, and per-instance lookup — exists in every language. Note the divergence on a missing instance id.

Operation Java Python Rust TypeScript
Global config subtree getGlobalConfig() get_global_config() global() global()
Instance ids getInstanceIds() get_instance_ids() instance_ids() instanceIds()
One instance subtree getInstanceConfig(id) get_instance_config(id) instance(id) instance(id)
Full raw document getFullConfig() get_full_config() raw field raw field
Missing-instance result returns null raises KeyError returns None returns undefined

The return types mirror each language’s JSON value: Java JsonObject (Gson), Python dict, Rust &serde_json::Value / Option<&Value>, TypeScript unknown / unknown | undefined.

Every value these methods return carries numbers in canonical form: a number whose value is integral and inside the signed/unsigned 64-bit integer range is delivered as an integer, regardless of the config source and identically in all four languages. Fractional values, and integral values outside that range, are delivered exactly as written.

An integer-typed setting therefore accepts any numeric encoding of an integral value — 5000, 5000.0, and 5e3 are the same setting — while a fractional value in an integer-typed setting is rejected rather than truncated.

ConfigManager config = gg.getConfigManager();
JsonObject global = config.getGlobalConfig();
long timeout = global.has("timeout") ? global.get("timeout").getAsLong() : 5000L;
for (String id : config.getInstanceIds()) {
JsonObject inst = config.getInstanceConfig(id); // null if id absent
}

The well-known config sections (logging, heartbeat, metricEmission, health, tags) are exposed as strongly-typed objects rather than raw JSON. Java/Python expose them as getters on the ConfigManager; Rust/TypeScript expose them as fields on the snapshot’s parsed view (RawConfig).

Section Java getter Python getter Rust field (cfg.parsed.…) TS field (cfg.parsed.…)
Logging getLoggingConfig()LoggingConfiguration get_logging_config() logging: LoggingConfig logging: LoggingConfig
Heartbeat getHeartbeatConfig()HeartbeatConfiguration get_heartbeat_config() heartbeat: HeartbeatConfig heartbeat: HeartbeatConfig
Metric emission getMetricConfig()MetricConfiguration get_metric_config() metric_emission: MetricConfig metricEmission: MetricConfig
Health getHealthConfig()HealthConfiguration get_health_config() health: HealthConfig health: HealthConfig
Tags getTagConfig()TagConfiguration get_tag_config() tags: BTreeMap<String, Value> tags: Record<string, unknown>
ConfigManager config = gg.getConfigManager();
LoggingConfiguration logging = config.getLoggingConfig();
HeartbeatConfiguration heartbeat = config.getHeartbeatConfig();
MetricConfiguration metric = config.getMetricConfig();
HealthConfiguration health = config.getHealthConfig();
TagConfiguration tags = config.getTagConfig();
int interval = heartbeat.getIntervalSecs();
String env = tags.getKeyValue("environment");

Substitute the built-in placeholders {ThingName}, {ComponentName} (the short name — the segment after the last .), {ComponentFullName}, and any {<tagKey>} from the tags section into config strings such as log paths and MQTT topics. Substituted values are sanitized (each of /, \, +, #, and control characters becomes _, then any remaining .. traversal becomes _); the template literal itself is left intact, so legitimate separators in the template survive.

The entry point diverges: Java/Python expose resolveTemplate / resolve_template as instance methods on the manager; Rust/TypeScript expose a free function resolve that takes a Config.

ConfigManager config = gg.getConfigManager();
// tags.environment from config is substituted into {environment}
String path = config.resolveTemplate(
"/var/log/{ComponentName}-{ThingName}-{environment}.log");

The resolved Thing name and component name are available everywhere; Java/Python additionally expose the resolved platform on the manager.

Value Java Python Rust TypeScript
Thing name getThingName() get_thing_name() cfg.thing_name (field) cfg.thingName (field)
Component name (short) getComponentName() get_component_name() — see note — see note
Component full name getComponentFullName() get_component_full_name() cfg.component_name (field) cfg.componentName (field)
UNS component identity getComponentIdentity()MessageIdentity get_component_identity() cfg.identity() cfg.componentIdentity (field)
Platform getPlatform()Platform get_platform() not on Config — see note not on Config — see note

The UNS component identity — the resolved hierarchy/identity values plus the component token, component-scoped with no instance token — is resolved once at construction, fail-fast on inconsistency, and is what the message builder stamps on every config-bound envelope. See the UNS API reference.

Register a listener to react to hot reloads. Three things diverge across languages: the method name/signature, whether the callback receives the new config, and where you register the listener.

Aspect Java Python Rust TypeScript
Method onConfigurationChanged() on_configuration_change(configuration) on_configuration_change(config) onConfigurationChange(config)
Receives new config? no — read back from manager yes — dict yes — Arc<Config> yes — Config
Async? no no yes (async) may return Promise<boolean> | boolean
Register on ConfigManager ConfigManager runtime gg runtime gg
Remove with removeConfigChangeListener(l) remove_config_change_listener(l) remove_config_change_listener(&l) removeConfigChangeListener(l)

The return value is informational only — it is logged but does not change reload behavior in any language.

public interface ConfigurationChangeListener {
// Takes NO argument — read back from the ConfigManager inside the callback.
boolean onConfigurationChanged();
}
ConfigManager configManager = gg.getConfigManager();
configManager.addConfigChangeListener(() -> {
// The callback takes no config argument — re-read from the manager.
JsonObject global = configManager.getGlobalConfig();
return true; // logged, but does not affect reload
});

All four SDKs expose only the merged effective config through the normal config APIs. Direct providers (FILE, ENV, CONFIGMAP, GG_CONFIG, and SHADOW) provide one effective document. CONFIG_COMPONENT receives a lineage bundle, deep-merges only layers[].config in order, validates the result, and then publishes or exposes the same effective shape as a single-document config.

Lineage bundles have this shape:

CONFIG_COMPONENT response body
{
"lineageVersion": 1,
"catalogVersion": "enterprise-site-zone-line-v1",
"component": "opcua-adapter",
"layers": [
{
"id": "enterprise/acme",
"kind": "scope",
"scope": { "enterprise": "acme" },
"config": {
"hierarchy": { "levels": ["enterprise", "site", "zone", "line", "device"] },
"identity": { "enterprise": "acme" },
"logging": { "level": "INFO" }
}
},
{
"id": "component/opcua-adapter",
"kind": "component",
"component": "opcua-adapter",
"config": {
"component": { "token": "opcua-adapter", "global": { "pollSecs": 5 } }
}
}
]
}

layers is ordered from the highest shared scope to the requested component. Each layer must declare id, kind, and object config; scope layers must declare object scope; the final and only component layer must name the requested component token. Legacy component-only documents and old {base, component} bundles are rejected. Structured server errors fail startup or reject the reload with the returned code and message. On reload, an invalid lineage bundle keeps the previous effective config.

Component authors normally receive a ready config object from the EdgeCommons builder/runtime and do not construct it directly. The underlying factory/builder is listed here for completeness.

Language Entry point
Java ConfigManagerFactory.create(componentName, cmdLine[, messagingClient]) — validates before the first apply
Python ConfigManagerBuilder.build(args, component_name) — returns the matching *ConfigManager subclass
Rust config::source::build(spec, messaging, thing, component) builds the source; Config::from_value(component_name, thing_name, raw) builds a snapshot
TypeScript buildConfigSource(spec, opts) builds the source; Config.fromValue(componentName, thingName, raw) builds a snapshot