Skip to content

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.

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 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 source

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
}

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.

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 all

A few behaviors worth knowing:

  • getByPath / get_by_path takes only a path prefix — there is no recursive argument at read time. Recursion is decided once, at sync-config time (see Choosing what to sync).
  • names requires the prefix argument in every language — pass "" explicitly to list everything.
  • Typed parsing: getInt is Long (Java, 64-bit), int (Python, arbitrary precision), i64 (Rust), and number (TypeScript — IEEE-754, so values above 2^53 lose precision; it validates with a strict integer regex first). getBool accepts true/1/yes/on and false/0/no/off (case-insensitive). getStringList splits on commas and trims; an empty string yields an empty list.
  • UTF-8: get errors on a non-UTF-8 value, getByPath silently skips non-decodable entries, and getBytes always returns the raw bytes.
  • Ordering: Java, Python, and Rust return names/getByPath results sorted. The TypeScript in-memory cache returns insertion order — do not rely on TypeScript ordering.

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());

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.

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.

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 }]
}
}
}

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 }. recursive defaults to true.

This is the only place recursion is configured — the read-time getByPath always reads whatever the cache already holds under a prefix.

Refresh is designed to never take a healthy component down:

  • A per-item failure is logged and increments the refresh_failures counter. 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 (default true) runs one refresh() inside the accessor at startup; if it fails it is caught and logged, and the service is still returned.
  • refreshIntervalSecs (default 300; 0 disables) 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(); // synchronous

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 credentials LocalVault (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".)

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 (so format!("{:?}", pv) would print the raw bytes) and TypeScript is a plain interface with no custom stringifier (logging it prints the value buffer). On Rust and TypeScript, do not log a ParamValue for a secure parameter.

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();