Parameters
The parameters subsystem (gg.parameters()) is an independent, opt-in, offline-first service
for externalized configuration — endpoints, feature flags, tuning knobs. It is the sibling of
credentials (which handles secrets): a pluggable source (env / mountedDir
/ awsSsm) sits behind a local cache, and reads are always served from the cache, never the
network. Your component keeps running with the last-known values even when the source is
unreachable.
Because it is opt-in, the accessor returns nothing unless a parameters section is present in your
config (and, in Rust, the parameters cargo feature is enabled). When it is off, your component
simply skips the calls.
Enabling parameters
Section titled “Enabling parameters”Add a parameters section to your component config. The minimum is a source. This config is
identical across all four languages (it validates against the single
config schema).
{ "parameters": { "source": { "type": "env", "prefix": "GG_PARAM_" }, "sync": { "names": ["/myapp/region", "/myapp/poolSize"] } }}The full set of keys: source.{type,region,endpointUrl,withDecryption,root,securePaths,prefix},
cache.{persist,path,keyProvider}, refreshIntervalSecs (default 300; 0 disables periodic
refresh), bootstrapOnStart (default true), and sync.{names,paths}.
Rust: enable the cargo feature
Section titled “Rust: enable the cargo feature”Rust gates the whole subsystem behind the parameters feature, and the AWS SSM source behind
parameters-aws. Without the feature, gg.parameters() always returns None.
[dependencies]ggcommons = { version = "*", features = ["parameters"] }# add "parameters-aws" as well if you use the awsSsm sourceGetting the service and the null contract
Section titled “Getting the service and the null contract”Fetch the service from your GGCommons instance. It is null / None / undefined whenever the
parameters section is absent (Rust: also when the feature is off) — always guard before use.
// getParameters() returns null when there is no `parameters` config section.ParameterService params = gg.getParameters();if (params == null) { return; // parameters subsystem is off}# get_parameters() returns None when there is no `parameters` config section.params = gg.get_parameters()if params is None: return # parameters subsystem is off// parameters() returns None with no `parameters` section, or if the `parameters` feature is off.let Some(params) = gg.parameters() else { return Ok(()); // parameters subsystem is off};// parameters() returns undefined when there is no `parameters` config section.const params = gg.parameters();if (!params) { return; // parameters subsystem is off}The accessor returns the ParameterService interface — depend on that, not the concrete
DefaultParameterService. The gg instance owns the service’s lifecycle (it stops the background
refresher at component shutdown), so you do not call close() yourself in the normal path.
Reading parameters
Section titled “Reading parameters”Parameters are addressed by an SSM-style path name like /myapp/region. get returns the raw
string; the typed accessors parse it. All four reject malformed values (a non-integer for getInt,
an unknown token for getBool).
Optional<String> region = params.get("/myapp/region");Optional<Long> poolSize = params.getInt("/myapp/poolSize");Optional<Boolean> debug = params.getBool("/myapp/debug");Optional<JsonElement> limits = params.getJson("/myapp/limits");Optional<List<String>> hosts = params.getStringList("/myapp/hosts");Optional<byte[]> raw = params.getBytes("/myapp/blob");
// A whole subtree by prefix, and the names under a prefix:Map<String, String> db = params.getByPath("/myapp/db");List<String> names = params.names("/myapp"); // pass "" for allregion = params.get("/myapp/region") # Optional[str]pool_size = params.get_int("/myapp/poolSize") # Optional[int]debug = params.get_bool("/myapp/debug") # Optional[bool]limits = params.get_json("/myapp/limits") # parsed JSON objecthosts = params.get_string_list("/myapp/hosts") # Optional[List[str]]raw = params.get_bytes("/myapp/blob") # Optional[bytes]
# A whole subtree by prefix, and the names under a prefix:db = params.get_by_path("/myapp/db") # Dict[str, str]names = params.names("/myapp") # List[str]; pass "" for alllet region = params.get("/myapp/region")?; // Option<String>let pool_size = params.get_int("/myapp/poolSize")?; // Option<i64>let debug = params.get_bool("/myapp/debug")?; // Option<bool>let limits = params.get_json("/myapp/limits")?; // Option<serde_json::Value>let hosts = params.get_string_list("/myapp/hosts")?; // Option<Vec<String>>let raw = params.get_bytes("/myapp/blob")?; // Option<Vec<u8>>
// A whole subtree by prefix, and the names under a prefix:let db = params.get_by_path("/myapp/db")?; // BTreeMap<String, String>let names = params.names("/myapp")?; // Vec<String>; pass "" for allconst region = params.get("/myapp/region"); // string | undefinedconst poolSize = params.getInt("/myapp/poolSize"); // number | undefinedconst debug = params.getBool("/myapp/debug"); // boolean | undefinedconst limits = params.getJson("/myapp/limits"); // unknown | undefinedconst hosts = params.getStringList("/myapp/hosts"); // string[] | undefinedconst raw = params.getBytes("/myapp/blob"); // Buffer | undefined
// A whole subtree by prefix, and the names under a prefix:const db = params.getByPath("/myapp/db"); // Map<string, string>const names = params.names("/myapp"); // string[]; pass "" for allA few behaviors worth knowing:
getByPath/get_by_pathtakes only a path prefix — there is norecursiveargument at read time. Recursion is decided once, at sync-config time (see Choosing what to sync).namesrequires the prefix argument in every language — pass""explicitly to list everything.- Typed parsing:
getIntisLong(Java, 64-bit),int(Python, arbitrary precision),i64(Rust), andnumber(TypeScript — IEEE-754, so values above2^53lose precision; it validates with a strict integer regex first).getBoolacceptstrue/1/yes/onandfalse/0/no/off(case-insensitive).getStringListsplits on commas and trims; an empty string yields an empty list. - UTF-8:
geterrors on a non-UTF-8 value,getByPathsilently skips non-decodable entries, andgetBytesalways returns the raw bytes. - Ordering: Java, Python, and Rust return
names/getByPathresults sorted. The TypeScript in-memory cache returns insertion order — do not rely on TypeScript ordering.
Inspecting refresh state
Section titled “Inspecting refresh state”stats() returns a non-sensitive ParameterStats (safe to log): a parameter count, the age of the
last successful refresh (null / None / undefined until the first fully-clean refresh), a
refresh-failure counter, and the source id.
ParameterStats st = params.stats();LOGGER.info("params source={} count={} failures={}", st.source(), st.parameterCount(), st.refreshFailures());st = params.stats()logger.info(f"params source={st.source} count={st.parameter_count} failures={st.refresh_failures}")let st = params.stats();tracing::info!(source = %st.source, count = st.parameter_count, failures = st.refresh_failures, "params");const st = params.stats();console.log(`params source=${st.source} count=${st.parameterCount} failures=${st.refreshFailures}`);Sources
Section titled “Sources”The source.type selects one of three built-in sources. env, mountedDir, and awsSsm are the
only config-driven sources — there is no custom type (see Custom sources for
the programmatic seam). Each config example below is the same in all four languages.
env — environment variables
Section titled “env — environment variables”Maps a parameter name to an environment variable: /myapp/db/host becomes
<PREFIX>MYAPP_DB_HOST (/, -, and . become _, then uppercase). The default prefix is
GG_PARAM_. Values from env are always non-secure.
{ "parameters": { "source": { "type": "env", "prefix": "GG_PARAM_" }, "sync": { "names": ["/myapp/region", "/myapp/db/host"] } }}With that config, params.get("/myapp/db/host") reads GG_PARAM_MYAPP_DB_HOST.
mountedDir — a mounted file tree
Section titled “mountedDir — a mounted file tree”Each file under root is a parameter: its bytes are the value, and its name is / plus the
path relative to root. Names under any securePaths prefix are marked secure=true. Dotfiles are
skipped (this safely ignores the kubelet ..data symlink farm on Kubernetes), and a directory at a
name is treated as “not a parameter”.
{ "parameters": { "source": { "type": "mountedDir", "root": "/etc/myapp/params", "securePaths": ["/myapp/db"] }, "sync": { "paths": ["/myapp"] } }}awsSsm — AWS Systems Manager Parameter Store
Section titled “awsSsm — AWS Systems Manager Parameter Store”Reads from SSM via GetParameter / GetParametersByPath (paginated). withDecryption (default
true) decrypts SecureString parameters, which are marked secure=true. region and an optional
endpointUrl (for floci / LocalStack / a VPC endpoint) configure the client; it otherwise uses the
default credential chain (TES on Greengrass).
{ "parameters": { "source": { "type": "awsSsm", "region": "us-east-1", "withDecryption": true }, "refreshIntervalSecs": 300, "sync": { "names": ["/myapp/region"], "paths": [{ "path": "/myapp/db", "recursive": true }] } }}Choosing what to sync
Section titled “Choosing what to sync”refresh() pulls only what you declare in sync:
sync.names— exact parameter names to fetch.sync.paths— path prefixes to fetch. Each entry is a bare string (recursive) or an object{ "path": "...", "recursive": true|false }.recursivedefaults totrue.
This is the only place recursion is configured — the read-time getByPath always reads whatever the
cache already holds under a prefix.
Offline-first refresh semantics
Section titled “Offline-first refresh semantics”Refresh is designed to never take a healthy component down:
- A per-item failure is logged and increments the
refresh_failurescounter. The error only propagates when the cache is still empty (the bootstrap case) — otherwise the previously cached values are kept. - A missing parameter (an unset env var, an SSM
ParameterNotFound) is not an error; the read just returns nothing. - Only a fully-clean refresh updates the last-refresh timestamp.
bootstrapOnStart(defaulttrue) runs onerefresh()inside the accessor at startup; if it fails it is caught and logged, and the service is still returned.refreshIntervalSecs(default300;0disables) drives a background daemon that refreshes periodically.
You can also refresh on demand. Note that only TypeScript’s refresh() is async (it returns
Promise<void>); the read accessors are synchronous in all four languages because they read the
already-loaded cache.
params.refresh(); // synchronousparams.refresh() # synchronousparams.refresh()?; // synchronous, returns Resultawait params.refresh(); // async in TypeScriptThe offline cache
Section titled “The offline cache”Every read is served from a local cache; the source is only touched on refresh(). The cache is
source-aware by default:
- A remote source (
awsSsm) → a persistent, encrypted cache that reuses the credentialsLocalVault(same on-disk format), so values survive restarts and remain usable offline. - A local source (
env/mountedDir) → an in-memory cache (the source is already local).
Set cache.persist to override the default either way. The default cache.path is the relative
"param-cache".
When the gg accessor builds the service, it resolves template variables like {ComponentFullName}
and {ThingName} in cache.path and the key path before opening the cache. (Calling the low-level
open factory directly does no templating — the raw default really is "param-cache".)
ParamValue and secure values
Section titled “ParamValue and secure values”Sources return values as a ParamValue carrying value (bytes), a secure flag, and an optional
version. The secure flag is set automatically — by a securePaths prefix (mountedDir) or a
SecureString (awsSsm). Component developers rarely touch ParamValue directly; it is the seam
between a source and the cache, and the typed accessors hand you plain strings, ints, and so on.
If you do handle a ParamValue, mind how each language treats secure values when logged:
- Java redacts the value in
toString(), and Python redacts it in__repr__— logging the value object will not leak the secret. - Rust derives
Debug(soformat!("{:?}", pv)would print the raw bytes) and TypeScript is a plain interface with no custom stringifier (logging it prints thevaluebuffer). On Rust and TypeScript, do not log aParamValuefor a secure parameter.
Custom sources
Section titled “Custom sources”A custom ParameterSource cannot be selected through config or reached via gg.parameters() — the
config-driven buildSource only knows env, mountedDir, and awsSsm. To inject your own source,
construct a DefaultParameterService directly with the public withMemoryCache /
withPersistentCache factories (these also back the open path and the test suites).
DefaultParameterService s = DefaultParameterService.withMemoryCache( myCustomSource, List.of("/myapp/region"), List.of());s.refresh();s = DefaultParameterService.with_memory_cache(my_custom_source, ["/myapp/region"], [])s.refresh()let s = DefaultParameterService::with_memory_cache( my_custom_source, vec!["/myapp/region".into()], vec![]);s.refresh()?;const s = DefaultParameterService.withMemoryCache(myCustomSource, ["/myapp/region"], []);await s.refresh();