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.
Reaching config from the facade
Section titled “Reaching config from the facade”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) |
Read methods
Section titled “Read methods”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.
Numbers in the returned document
Section titled “Numbers in the returned document”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.
Reading config
Section titled “Reading config”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}config = gg.get_config_manager()global_cfg = config.get_global_config()timeout = global_cfg.get("timeout", 5000)
for inst_id in config.get_instance_ids(): inst = config.get_instance_config(inst_id) # raises KeyError if id absentlet cfg = gg.config(); // Arc<Config> snapshotlet _global = cfg.global(); // &serde_json::Value
for id in cfg.instance_ids() { let _inst = cfg.instance(&id); // Option<&Value> — None if id absent}const cfg = gg.config(); // Config snapshotconst _global = cfg.global(); // unknown
for (const id of cfg.instanceIds()) { const _inst = cfg.instance(id); // unknown | undefined — undefined if id absent}Typed section accessors
Section titled “Typed section accessors”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");config = gg.get_config_manager()
logging_cfg = config.get_logging_config()heartbeat = config.get_heartbeat_config()metric = config.get_metric_config()health = config.get_health_config()tags = config.get_tag_config()let cfg = gg.config();
let logging = &cfg.parsed.logging;let heartbeat = &cfg.parsed.heartbeat;let metric = &cfg.parsed.metric_emission;let health = &cfg.parsed.health;let tags = &cfg.parsed.tags; // BTreeMap<String, Value>const cfg = gg.config();
const logging = cfg.parsed.logging;const heartbeat = cfg.parsed.heartbeat;const metric = cfg.parsed.metricEmission;const health = cfg.parsed.health;const tags = cfg.parsed.tags; // Record<string, unknown>Template resolution
Section titled “Template resolution”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");config = gg.get_config_manager()path = config.resolve_template("/data/{ThingName}/{ComponentName}.log")use edgecommons::config::template;
let cfg = gg.config();let path = template::resolve(&cfg, "heartbeat/{ThingName}/{ComponentName}");import { resolve } from "@edgecommons/edgecommons";
const cfg = gg.config();const path = resolve(cfg, "/data/{ThingName}/{ComponentName}.log");Identity and platform
Section titled “Identity and platform”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.
ConfigurationChangeListener
Section titled “ConfigurationChangeListener”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.
Interface declarations
Section titled “Interface declarations”public interface ConfigurationChangeListener { // Takes NO argument — read back from the ConfigManager inside the callback. boolean onConfigurationChanged();}class ConfigurationChangeListener(metaclass=abc.ABCMeta): @abstractmethod def on_configuration_change(self, configuration) -> bool: ... # `configuration` is the new config dict#[async_trait]pub trait ConfigurationChangeListener: Send + Sync { async fn on_configuration_change(&self, config: Arc<Config>) -> bool;}interface ConfigurationChangeListener { onConfigurationChange(config: Config): Promise<boolean> | boolean;}Registering a listener
Section titled “Registering a listener”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});class MyApp(ConfigurationChangeListener): def on_configuration_change(self, configuration) -> bool: # `configuration` is the new config dict return True
config_manager = gg.get_config_manager()config_manager.add_config_change_listener(MyApp())use std::sync::Arc;use async_trait::async_trait;use edgecommons::config::{Config, ConfigurationChangeListener};
struct MyListener;
#[async_trait]impl ConfigurationChangeListener for MyListener { async fn on_configuration_change(&self, config: Arc<Config>) -> bool { let _ids = config.instance_ids(); true }}
gg.add_config_change_listener(Arc::new(MyListener));gg.addConfigChangeListener({ onConfigurationChange: (config) => { const _ids = config.instanceIds(); return true; },});Hierarchical config contract
Section titled “Hierarchical config contract”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:
{ "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.
Construction
Section titled “Construction”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 |
See also
Section titled “See also”- Configuration guide — sources, document layout, hot reload, validation
- Configuration schema reference — every config field
- CLI contract — the
-c/--config,--platform, and--transportflags