From 992d83d8304fdb08cf110782b2d58e7bcf5ea11b Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 3 Jun 2026 15:23:08 +0000 Subject: [PATCH] docs: cover typed family descriptors and @StableApi Update registry and model docs to point custom collectors at getMetricFamilyDescriptor(), noting the deprecated fragmented metadata methods bridge for compatibility. Add an internals/stability page covering @StableApi, the mise run api-diff baseline check, and the breaking-api-change-accepted label. Signed-off-by: Gregor Zeitlinger --- docs/content/getting-started/registry.md | 46 +++++++++++++++--------- docs/content/internals/model.md | 8 ++--- docs/content/internals/stability.md | 36 +++++++++++++++++++ 3 files changed, 70 insertions(+), 20 deletions(-) create mode 100644 docs/content/internals/stability.md diff --git a/docs/content/getting-started/registry.md b/docs/content/getting-started/registry.md index f50c8fe4f..de437be23 100644 --- a/docs/content/getting-started/registry.md +++ b/docs/content/getting-started/registry.md @@ -104,25 +104,39 @@ Validation of duplicate metric names and label schemas happens at registration t Built-in metrics (Counter, Gauge, Histogram, etc.) participate in this validation. Custom collectors that implement the `Collector` or `MultiCollector` interface can optionally -implement `getPrometheusName()` and `getMetricType()` (and the MultiCollector per-name variants) so -the registry can enforce consistency. **Validation is skipped when metric name or type is -unavailable:** if `getPrometheusName()` or `getMetricType()` returns `null`, the registry does not -validate that collector. If two such collectors produce the same metric name and same label set at -scrape time, the exposition output may contain duplicate time series and be invalid for Prometheus. - -When validation _is_ performed (name and type are non-null), **null label names are treated as an -empty label schema:** `getLabelNames()` returning `null` is normalized to `Collections.emptySet()` -and full label-schema validation and duplicate detection still apply. A collector that returns a -non-null type but leaves `getLabelNames()` as `null` is still validated, with its labels treated as -empty. +expose their registration-time metadata so the registry can enforce consistency. The recommended +way is to override `getMetricFamilyDescriptor()` (or `getMetricFamilyDescriptors()` on +`MultiCollector`) and return a `MetricFamilyDescriptor` describing the metric name, type, label +names, and metadata (help, unit) the collector will emit at scrape time. + +```java +@Override +public MetricFamilyDescriptor getMetricFamilyDescriptor() { + return MetricFamilyDescriptor.gauge("my_metric") + .help("Example metric") + .labelNames("region") + .build(); +} +``` + +The fragmented `getPrometheusName()`, `getMetricType()`, `getLabelNames()`, and `getMetadata()` +methods (and their `MultiCollector` per-name variants) are deprecated. They remain bridged by a +default implementation of `getMetricFamilyDescriptor()` for compatibility, so existing +collectors keep working unchanged. + +**Validation is skipped when registration-time metadata is unavailable:** if +`getMetricFamilyDescriptor()` returns `null` (the default when name or type is missing), the +registry does not validate that collector. If two such collectors produce the same metric name and +same label set at scrape time, the exposition output may contain duplicate time series and be +invalid for Prometheus. This is also relevant for downstream adapter libraries that bridge to this registry. If an adapter implements `MultiCollector`, its registration-time metadata must match the metric families it will -actually emit at scrape time. In practice, that means `getPrometheusNames()`, `getMetricType(...)`, -`getLabelNames(...)`, and `getMetadata(...)` need to describe the same names, types, labels, and -suffix behavior as the eventual `MetricSnapshot` output. Otherwise an adapter may pass or fail -collision checks differently after upgrading to a newer client_java release, even if its scrape -output logic did not change. +actually emit at scrape time. In practice, the `MetricFamilyDescriptor`s returned from +`getMetricFamilyDescriptors()` need to describe the same names, types, labels, and suffix behavior +as the eventual `MetricSnapshot` output. Otherwise an adapter may pass or fail collision checks +differently after upgrading to a newer client_java release, even if its scrape output logic did not +change. ## Unregistering a Metric diff --git a/docs/content/internals/model.md b/docs/content/internals/model.md index 9d4061bcc..629e87bf0 100644 --- a/docs/content/internals/model.md +++ b/docs/content/internals/model.md @@ -19,10 +19,10 @@ All metric types implement the [Collector](/client_java/api/io/prometheus/metrics/model/registry/Collector.html) interface, i.e. they provide a [collect()]() -method to produce snapshots. Implementers that do not provide metric type or label names (returning -null from `getMetricType()` and `getLabelNames()`) are not validated at registration; they must -avoid producing the same metric name and label schema as another collector, or exposition may be -invalid. +method to produce snapshots. Implementers expose their registration-time metadata via +`getMetricFamilyDescriptor()` (or `getMetricFamilyDescriptors()` on `MultiCollector`). When that +returns `null`, the collector is not validated at registration and must avoid producing the same +metric name and label schema as another collector, or exposition may be invalid. ## prometheus-metrics-model diff --git a/docs/content/internals/stability.md b/docs/content/internals/stability.md new file mode 100644 index 000000000..7823f936a --- /dev/null +++ b/docs/content/internals/stability.md @@ -0,0 +1,36 @@ +--- +title: API stability +weight: 2 +--- + +The published Java API surface is marked with the +[`@StableApi`](/client_java/api/io/prometheus/metrics/annotations/StableApi.html) annotation. The +annotation is opt-in: only annotated types and members are part of the stable, published API and +follow semantic versioning — backwards-incompatible changes happen only in a major version bump. +Unannotated public types are not part of the stability contract and may change in any release. + +`@StableApi` can be applied to a type to publish the type and its members, or to individual +constructors, methods, and fields when only part of a public type is stable. + +## API diff check + +CI runs [japicmp](https://siom79.github.io/japicmp/) against a pinned baseline release and fails on +incompatible changes to the `@StableApi` surface. Run it locally with: + +```bash +mise run api-diff +``` + +Reports are written to `**/target/japicmp/*`. + +The baseline version is tracked in `pom.xml` and updated by Renovate; the published baseline diffs +are stored under `docs/apidiffs/`. + +## Accepting breaking changes + +Backwards-incompatible changes to the `@StableApi` surface are only allowed in a major version +bump. Within a major version line, the API diff check must pass. + +When a major version is being prepared, intentional incompatible changes can be accepted by adding +the `breaking-api-change-accepted` label to the pull request. The label is not an escape hatch for +minor or patch releases.