Errors & exceptions
The error taxonomy is how your component tells which subsystem failed — config, schema validation, messaging, credentials, parameters, or streaming — and decides whether to recover or abort. Each SDK exposes the same set of failure modes, but in its own idiomatic shape, so the type you catch differs by language even when the underlying cause is identical.
There is no single, unified error type across the four languages. Only Rust unifies everything into one enum; TypeScript unifies the core subsystems only; Java and Python use a distinct exception class per subsystem.
How errors are shaped per language
Section titled “How errors are shaped per language”A small set of subsystem exceptions in com.mbreissi.ggcommons. Some are checked (the compiler
forces a try/catch or throws), some are unchecked (RuntimeException).
// package com.mbreissi.ggcommons.*class ConfigurationException extends Exception // CHECKED — config.ConfigurationExceptionclass ConfigurationValidator.ConfigurationValidationException // CHECKED — nested static in config.ConfigurationValidator extends Exceptionclass CredentialException extends RuntimeException // unchecked — credentials.CredentialExceptionclass ParameterException extends RuntimeException // unchecked — parameters.ParameterExceptionclass GgStreamException extends RuntimeException { // unchecked — streaming.GgStreamException GgStreamException(int code, String message); int code(); // the underlying ggsl_status value // constants mirroring ggsl_status: // OK=0, ERR_CONFIG=1, ERR_IO=2, ERR_CORRUPT=3, ERR_FULL=4, // ERR_UNKNOWN_STREAM=5, ERR_SINK=6, ERR_PANIC=7, ERR_INVALID_ARG=8}CLI and builder preconditions throw the standard IllegalArgumentException (e.g. unknown
--platform / --transport, the removed -m/--mode flag) and IllegalStateException (builder
precondition violations).
Subsystem Exception subclasses, each exported from its package.
class CredentialError(Exception): ... # message-only
# ggcommons.parametersclass ParameterError(Exception): ... # message-only
# ggcommons.streamingclass GgStreamError(Exception): def __init__(self, code: int, message: str | None = None) # instance attributes: .code (int), .message
# ggcommons.validation.configuration_validatorclass ConfigurationValidationException(Exception): def __init__(self, message: str, validation_errors: list | None = None) # instance attribute: .validation_errorsPython has no dedicated config-load exception class. Config-load failures surface as the
built-in ValueError, as ConfigurationValidationException, or as a propagated stdlib exception.
Using an accessor before the runtime is initialized raises RuntimeError("GGCommons not properly initialized").
One library-wide error enum plus a Result alias in ggcommons. Fallibility is encoded in the
return type — there are no separate per-subsystem error types.
// ggcommonspub type Result<T> = std::result::Result<T, GgError>;
#[derive(Debug, thiserror::Error)]pub enum GgError { Cli(String), // CLI parsing / contract violation Config(String), // config loading / shape Validation(String), // JSON-schema validation failure Messaging(String), Metrics(String), Ipc(String), // Greengrass IPC Streaming(String), // `streaming` feature Credentials(String), // `credentials` feature Parameters(String), // `parameters` feature Io(std::io::Error), // #[from] — auto-converts via `?` Json(serde_json::Error), // #[from] — auto-converts via `?`}GgError is Send + Sync; constructing or matching on it cannot panic. The #[from] on Io and
Json means stdlib I/O and serde_json errors convert into a GgError automatically through the
? operator.
Two families. The core subsystems share one tagged GgError; credentials, parameters, and
streaming are standalone Error subclasses that are not part of the tag union.
// @mbreissi/ggcommonstype GgErrorKind = | "Cli" | "Config" | "Validation" | "Messaging" | "Metrics" | "Ipc" | "Io" | "Json"; // NOTE: no Streaming/Credentials/Parameters
class GgError extends Error { readonly kind: GgErrorKind; // this.name === `GgError(${kind})` // static factories: GgError.cli / config / validation / messaging / // metrics / ipc / io / json}
// separate Error subclasses (NOT covered by GgErrorKind):class CredentialError extends Error {} // this.name = "CredentialError"class ParameterError extends Error {} // this.name = "ParameterError"class GgStreamError extends Error { // this.name = "GgStreamError" readonly code: number; // the underlying ggsl_status value}Because GgErrorKind omits Streaming/Credentials/Parameters, a check like
e.kind === "Streaming" will never match — those failures are the separate classes above. Use
instanceof GgStreamError / instanceof CredentialError / instanceof ParameterError instead.
Where each subsystem’s failure surfaces
Section titled “Where each subsystem’s failure surfaces”The same logical failure maps to a different type in each SDK. Read across a row to translate.
| Subsystem failure | Java | Python | Rust | TypeScript |
|---|---|---|---|---|
| CLI / arg validation | IllegalArgumentException |
ValueError |
GgError::Cli |
GgError kind "Cli" |
| Config load | ConfigurationException (checked) |
ValueError / propagated |
GgError::Config |
GgError kind "Config" |
| Schema validation | ConfigurationValidationException (checked) |
ConfigurationValidationException |
GgError::Validation |
GgError kind "Validation" |
| Messaging | standard exception (no dedicated class) | standard exception (no dedicated class) | GgError::Messaging |
GgError kind "Messaging" |
| Metrics | standard exception (no dedicated class) | standard exception (no dedicated class) | GgError::Metrics |
GgError kind "Metrics" |
| Greengrass IPC | standard exception (no dedicated class) | standard exception (no dedicated class) | GgError::Ipc |
GgError kind "Ipc" |
| Credentials | CredentialException |
CredentialError |
GgError::Credentials |
CredentialError |
| Parameters | ParameterException |
ParameterError |
GgError::Parameters |
ParameterError |
| Streaming | GgStreamException (+code()) |
GgStreamError (+.code) |
GgError::Streaming (code discarded) |
GgStreamError (+.code) |
| I/O · JSON | JDK IOException etc. |
stdlib exceptions | GgError::Io / GgError::Json |
GgError kind "Io" / "Json" |
When each is thrown
Section titled “When each is thrown”ConfigurationException— thrown byConfigManagerFactory.create(...)when no configuration is found, or when aConfigManagercannot be created. It is not thrown byConfigManageritself.ConfigurationValidationException— thrown byConfigurationValidator.validate(JsonObject)when the config violates the canonical JSON schema. It is fail-closed: a missing schema resource on the classpath also throws (it does not silently pass).CredentialException— vault open/decrypt failures: a wrong KEK, tampered data, an unsupported vault format, or corrupt JSON. Messages never include secret or key material.ParameterException— parameter resolution/decryption failures (the parameters subsystem reuses the credentials vault as an encrypted cache).GgStreamException— any non-zeroggsl_statusfrom the nativeggstreamlogengine; inspectcode()(e.g.ERR_UNKNOWN_STREAM,ERR_CONFIG,ERR_FULL) to branch.
ConfigurationValidationException— raised byConfigurationValidator.validate(config)andvalidate_section(...)on a schema violation; fail-closed whenjsonschemaor the schema resource is missing. ANoneconfig raises the built-inValueErrorinstead.CredentialError— vault failures (wrong KEK, tamper, unsupported format, corrupt data).ParameterError— parameter resolution/decryption failures.GgStreamError— native streaming failures; check.code(e.g.ERR_UNKNOWN_STREAMis5,ERR_CONFIGis1). It is also raised with.code == -1when the native streaming wheel is not installed.ValueError—None/empty inputs (validation, an empty component name in the builder, and other argument checks).RuntimeError—"GGCommons not properly initialized"when an accessor is used before init.
Every fallible call returns Result<T, GgError>; match on the variant to tell subsystems apart.
GgError::Cli— CLI parsing or contract violations (unknown platform/transport, etc.).GgError::Config— config loading / shape problems.GgError::Validation— JSON-schema validation failures.GgError::Messaging/GgError::Metrics/GgError::Ipc— the respective subsystem failed;messaging()itself returnsErr(GgError::Messaging(...))when the transport is unwired.GgError::Streaming/GgError::Credentials/GgError::Parameters— the matching feature-gated subsystem failed (these variants exist only when the corresponding cargo feature is enabled).GgError::Io/GgError::Json— propagated stdlib I/O orserde_jsonerrors, converted automatically by?.
GgError(kind"Validation") — thrown byvalidate(...)on a schema violation.GgError(kind"Cli"/"Config") — CLI/arg and config-load problems.GgError(kind"Messaging"/"Metrics"/"Ipc") — the respective subsystem failed;messaging()throwsGgError.messaging(...)when the transport is unwired, whilemetrics()returns the (always-wired) metric service.GgError(kind"Io"/"Json") — I/O and JSON failures.CredentialError— vault failures (wrong KEK, tamper, unsupported format).ParameterError— parameter resolution/decryption failures.GgStreamError— native streaming failures; check.code. The helpertranslate(e)parses a nativeggsl:<code>:<message>string into aGgStreamError.
The streaming status code
Section titled “The streaming status code”Streaming is the one place that carries a numeric status (ggsl_status from the native
ggstreamlog engine) — but only three of the four SDKs preserve it.
GgStreamException.code() returns the int, and named constants are exposed
(GgStreamException.ERR_FULL, ERR_UNKNOWN_STREAM, …) so you can branch on a name.
GgStreamException ex = assertThrows(GgStreamException.class, () -> svc.stats("nope"));assertEquals(GgStreamException.ERR_UNKNOWN_STREAM, ex.code()); // 5GgStreamError.code holds the int, but no named ERR_* constants are exposed — compare against
the literal integers (or declare your own).
from ggcommons.streaming import GgStreamError
with pytest.raises(GgStreamError) as ei: svc.stats("nope")assert ei.value.code == 5 # ERR_UNKNOWN_STREAM — no named constant in ggcommons.streamingThe code is discarded. Streaming failures stringify into GgError::Streaming(String), so a Rust
caller can read the message but cannot branch on the numeric ggsl_status.
// streaming errors funnel into GgError::Streaming(e.to_string()) — no numeric code is retainedif let Err(ggcommons::GgError::Streaming(msg)) = result { eprintln!("streaming failed: {msg}");}GgStreamError.code holds the int (no named constants).
import { GgStreamError } from "@mbreissi/ggcommons";
// e was thrown by a streaming call (or produced by translate(...))expect((e as GgStreamError).code).toBe(5); // ERR_UNKNOWN_STREAMOptional subsystems return empty, they do not throw
Section titled “Optional subsystems return empty, they do not throw”Credentials, parameters, and streaming are opt-in: when the matching config section is absent (and, in Rust, the matching cargo feature is enabled), the accessor returns an empty value rather than throwing. Always check before use.
getStreams(), getCredentials(), and getParameters() return null when the section is
absent. getMessaging() and getMetrics() return the wired subsystem.
var creds = gg.getCredentials();if (creds == null) { // no `credentials` config section — feature is off} else { var secret = creds.get("db-password");}get_streams(), get_credentials(), and get_parameters() return None when the section is
absent.
creds = gg.get_credentials()if creds is None: pass # no `credentials` config section — feature is offelse: secret = creds.get("db-password")credentials() and parameters() return Option; streams() is gated behind the streaming
feature. By contrast messaging() returns a Result and yields Err(GgError::Messaging(...))
when the transport is unwired.
match gg.credentials() { None => { /* no `credentials` config section — feature is off */ } Some(creds) => { let _secret = creds.get("db-password")?; }}streams(), credentials(), and parameters() return undefined when the section is absent.
By contrast messaging() throws GgError.messaging(...) when the transport is unwired.
const creds = gg.credentials();if (creds === undefined) { // no `credentials` config section — feature is off} else { const secret = await creds.get("db-password");}Handling errors
Section titled “Handling errors”A minimal, idiomatic handler in each language — catch the validation error to abort startup, and branch on the streaming status code where it is available.
import com.mbreissi.ggcommons.config.ConfigurationValidator;import com.mbreissi.ggcommons.config.ConfigurationValidator.ConfigurationValidationException;import com.mbreissi.ggcommons.streaming.GgStreamException;
// Checked: the compiler forces you to handle (or declare) this one.try { ConfigurationValidator.validate(config); // JsonObject} catch (ConfigurationValidationException e) { log.error("config rejected: {}", e.getMessage()); // lists each schema violation throw new IllegalStateException("cannot start", e); // abort startup}
// Unchecked, code-bearing.try { var stats = gg.getStreams().stats("telemetry");} catch (GgStreamException e) { if (e.code() == GgStreamException.ERR_FULL) { // durable buffer is full — back off and retry later } else { throw e; }}from ggcommons.validation.configuration_validator import ( ConfigurationValidator, ConfigurationValidationException,)from ggcommons.streaming import GgStreamError
try: ConfigurationValidator.validate(config)except ConfigurationValidationException as e: # e.validation_errors carries the individual violations raise SystemExit(f"config rejected: {e}")
try: stats = gg.get_streams().stats("telemetry")except GgStreamError as e: if e.code == 4: # ERR_FULL — no named constant exported pass # back off and retry later else: raiseuse ggcommons::{GgCommonsBuilder, GgError};
let gg = match GgCommonsBuilder::new("com.example.MyComponent") .args(std::env::args_os()) .build() .await{ Ok(gg) => gg, Err(GgError::Cli(msg)) => { eprintln!("bad CLI args: {msg}"); std::process::exit(2); // the app decides to exit — the library never does } Err(GgError::Config(msg)) | Err(GgError::Validation(msg)) => { eprintln!("config rejected: {msg}"); std::process::exit(1); } Err(e) => { eprintln!("startup failed: {e}"); std::process::exit(1); }};import { GgError, GgStreamError } from "@mbreissi/ggcommons";
try { validate(config);} catch (e) { if (e instanceof GgError && e.kind === "Validation") { console.error(`config rejected: ${e.message}`); process.exit(1); // the app decides to exit — the library never does } throw e;}
try { const stats = await gg.streams()?.stats("telemetry");} catch (e) { // streaming is a separate class, NOT GgError kind "Streaming" if (e instanceof GgStreamError && e.code === 4 /* ERR_FULL */) { // back off and retry later } else { throw e; }}Process exit and SIGTERM
Section titled “Process exit and SIGTERM”Whether a failure (or shutdown) ends the OS process differs sharply across the SDKs. This matters when a supervisor (the Nucleus, a container runtime, Kubernetes) watches the exit code.