Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 30 additions & 16 deletions docs/content/getting-started/registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 4 additions & 4 deletions docs/content/internals/model.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()](</client_java/api/io/prometheus/metrics/model/registry/Collector.html#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

Expand Down
36 changes: 36 additions & 0 deletions docs/content/internals/stability.md
Original file line number Diff line number Diff line change
@@ -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.
Loading