From 18aeab7f2747600c961feef885fdd23a4012c5bd Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 8 Jun 2026 08:57:51 +0000 Subject: [PATCH 1/7] feat: add MetricMetadata.Builder, deprecate wide constructors The 4-arg and 5-arg constructors require callers to pre-compute expositionBaseName and originalName, leaking internal naming logic into every caller site. The new builder encapsulates that logic: - name(): base name (unit appended if absent) - unit(): appended to name when not already present - counterSuffix(true): sets expositionBaseName to name + "_total", so the exposition writer knows to preserve the suffix rather than double-append it (relevant for UTF-8 metric names) Existing callers (MetricMetadataSupport, MetricMetadata.escape) keep the deprecated constructors with @SuppressWarnings("deprecation"). Signed-off-by: Gregor Zeitlinger --- .../prometheus-metrics-model.txt | 17 +++- .../model/snapshots/MetricMetadata.java | 81 +++++++++++++++++++ .../snapshots/MetricMetadataSupport.java | 1 + .../model/snapshots/MetricMetadataTest.java | 68 ++++++++++++++++ 4 files changed, 166 insertions(+), 1 deletion(-) diff --git a/docs/apidiffs/current_vs_latest/prometheus-metrics-model.txt b/docs/apidiffs/current_vs_latest/prometheus-metrics-model.txt index 25f90822c..f0ba4c4e8 100644 --- a/docs/apidiffs/current_vs_latest/prometheus-metrics-model.txt +++ b/docs/apidiffs/current_vs_latest/prometheus-metrics-model.txt @@ -1,2 +1,17 @@ Comparing source compatibility of prometheus-metrics-model-1.7.1-SNAPSHOT.jar against prometheus-metrics-model-1.7.0.jar -No changes. +*** MODIFIED CLASS: PUBLIC FINAL io.prometheus.metrics.model.snapshots.MetricMetadata (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + === UNCHANGED CONSTRUCTOR: PUBLIC MetricMetadata(java.lang.String, java.lang.String, java.lang.String, io.prometheus.metrics.model.snapshots.Unit) + +++ NEW ANNOTATION: java.lang.Deprecated + === UNCHANGED CONSTRUCTOR: PUBLIC MetricMetadata(java.lang.String, java.lang.String, java.lang.String, java.lang.String, io.prometheus.metrics.model.snapshots.Unit) + +++ NEW ANNOTATION: java.lang.Deprecated + +++ NEW METHOD: PUBLIC(+) STATIC(+) io.prometheus.metrics.model.snapshots.MetricMetadata$Builder builder() ++++ NEW CLASS: PUBLIC(+) STATIC(+) FINAL(+) io.prometheus.metrics.model.snapshots.MetricMetadata$Builder (not serializable) + +++ CLASS FILE FORMAT VERSION: 52.0 <- n.a. + +++ NEW SUPERCLASS: java.lang.Object + +++ NEW METHOD: PUBLIC(+) io.prometheus.metrics.model.snapshots.MetricMetadata build() + +++ NEW METHOD: PUBLIC(+) io.prometheus.metrics.model.snapshots.MetricMetadata$Builder counterSuffix(boolean) + +++ NEW METHOD: PUBLIC(+) io.prometheus.metrics.model.snapshots.MetricMetadata$Builder help(java.lang.String) + +++ NEW METHOD: PUBLIC(+) io.prometheus.metrics.model.snapshots.MetricMetadata$Builder name(java.lang.String) + +++ NEW METHOD: PUBLIC(+) io.prometheus.metrics.model.snapshots.MetricMetadata$Builder unit(io.prometheus.metrics.model.snapshots.Unit) + diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java index 3a44a2402..3f9499b31 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java @@ -73,6 +73,82 @@ public MetricMetadata(String name, @Nullable String help, @Nullable Unit unit) { this(name, name, help, unit); } + /** + * Creates a builder for {@link MetricMetadata}. + * + *

Use the builder instead of the multi-arg constructors for cleaner, more readable code: + * + *

{@code
+   * MetricMetadata.builder()
+   *     .name("http_requests")
+   *     .help("Total HTTP requests")
+   *     .unit(Unit.BYTES)
+   *     .counterSuffix(true)
+   *     .build();
+   * }
+ */ + @StableApi + public static Builder builder() { + return new Builder(); + } + + /** Builder for {@link MetricMetadata}. */ + @StableApi + public static final class Builder { + @Nullable private String name; + @Nullable private String help; + @Nullable private Unit unit; + private boolean counterSuffix; + + private Builder() {} + + /** Required. The base metric name (without type suffix like {@code _total}). */ + public Builder name(String name) { + this.name = name; + return this; + } + + /** Optional. Human-readable description of the metric. */ + public Builder help(String help) { + this.help = help; + return this; + } + + /** Optional. The unit of measurement. Appended to the name if not already present. */ + public Builder unit(Unit unit) { + this.unit = unit; + return this; + } + + /** + * Optional. When {@code true}, the writer appends {@code _total} to the exposition name. Use + * this for counter metrics, especially UTF-8 names where the writer cannot infer it from the + * snapshot type alone. + */ + public Builder counterSuffix(boolean counterSuffix) { + this.counterSuffix = counterSuffix; + return this; + } + + /** Builds the {@link MetricMetadata}. Throws if {@code name} was not set. */ + public MetricMetadata build() { + if (name == null) { + throw new IllegalArgumentException("name is required"); + } + String baseName = name; + if (unit != null && !baseName.endsWith("_" + unit) && !baseName.endsWith("." + unit)) { + baseName = baseName + "_" + unit; + } + String expositionBaseName = baseName; + if (counterSuffix + && !expositionBaseName.endsWith("_total") + && !expositionBaseName.endsWith(".total")) { + expositionBaseName = expositionBaseName + "_total"; + } + return new MetricMetadata(baseName, expositionBaseName, name, help, unit); + } + } + /** * Constructor with exposition base name. * @@ -82,7 +158,9 @@ public MetricMetadata(String name, @Nullable String help, @Nullable Unit unit) { * format writers for smart-append logic * @param help optional. May be {@code null}. * @param unit optional. May be {@code null}. + * @deprecated Use {@link #builder()} instead. */ + @Deprecated public MetricMetadata( String name, String expositionBaseName, @Nullable String help, @Nullable Unit unit) { this(name, expositionBaseName, expositionBaseName, help, unit); @@ -97,7 +175,9 @@ public MetricMetadata( * @param originalName the raw name as provided by the user, before any modification * @param help optional. May be {@code null}. * @param unit optional. May be {@code null}. + * @deprecated Use {@link #builder()} instead. */ + @Deprecated public MetricMetadata( String name, String expositionBaseName, @@ -205,6 +285,7 @@ private void validate() { } } + @SuppressWarnings("deprecation") MetricMetadata escape(EscapingScheme escapingScheme) { return new MetricMetadata( PrometheusNaming.escapeName(name, escapingScheme), diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadataSupport.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadataSupport.java index 7fa0df6f0..96083416f 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadataSupport.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadataSupport.java @@ -18,6 +18,7 @@ static MetricMetadata infoMetadata(String name, @Nullable String help) { return typedMetadata(name, help, null, "_info", ".info"); } + @SuppressWarnings("deprecation") private static MetricMetadata typedMetadata( String originalName, @Nullable String help, diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java index 5781eb146..0017b04bd 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java @@ -83,6 +83,7 @@ void testUnitNotDuplicated() { assertThat(sanitizeMetricName("my_counter_bytes", Unit.BYTES)).isEqualTo("my_counter_bytes"); } + @SuppressWarnings("deprecation") @Test void testFiveArgConstructor() { MetricMetadata metadata = @@ -94,6 +95,7 @@ void testFiveArgConstructor() { assertThat(metadata.getUnit()).isEqualTo(Unit.BYTES); } + @SuppressWarnings("deprecation") @Test void testFourArgConstructorDefaultsOriginalName() { MetricMetadata metadata = new MetricMetadata("req_bytes", "req_bytes", "help", Unit.BYTES); @@ -107,4 +109,70 @@ void testThreeArgConstructorDefaultsOriginalName() { assertThat(metadata.getOriginalName()).isEqualTo("req_bytes"); assertThat(metadata.getExpositionBaseName()).isEqualTo("req_bytes"); } + + @Test + void builder_noUnit() { + MetricMetadata m = MetricMetadata.builder().name("requests").help("total requests").build(); + assertThat(m.getName()).isEqualTo("requests"); + assertThat(m.getExpositionBaseName()).isEqualTo("requests"); + assertThat(m.getOriginalName()).isEqualTo("requests"); + assertThat(m.getHelp()).isEqualTo("total requests"); + assertThat(m.getUnit()).isNull(); + } + + @Test + void builder_unitAppendedWhenAbsent() { + MetricMetadata m = MetricMetadata.builder().name("requests").unit(Unit.BYTES).build(); + assertThat(m.getName()).isEqualTo("requests_bytes"); + assertThat(m.getExpositionBaseName()).isEqualTo("requests_bytes"); + assertThat(m.getOriginalName()).isEqualTo("requests"); + } + + @Test + void builder_unitNotDuplicatedWhenPresent() { + MetricMetadata m = MetricMetadata.builder().name("requests_bytes").unit(Unit.BYTES).build(); + assertThat(m.getName()).isEqualTo("requests_bytes"); + assertThat(m.getExpositionBaseName()).isEqualTo("requests_bytes"); + assertThat(m.getOriginalName()).isEqualTo("requests_bytes"); + } + + @Test + void builder_counterSuffixAppended() { + MetricMetadata m = MetricMetadata.builder().name("requests").counterSuffix(true).build(); + assertThat(m.getName()).isEqualTo("requests"); + assertThat(m.getExpositionBaseName()).isEqualTo("requests_total"); + assertThat(m.getOriginalName()).isEqualTo("requests"); + } + + @Test + void builder_counterSuffixAndUnit() { + MetricMetadata m = + MetricMetadata.builder().name("requests").unit(Unit.BYTES).counterSuffix(true).build(); + assertThat(m.getName()).isEqualTo("requests_bytes"); + assertThat(m.getExpositionBaseName()).isEqualTo("requests_bytes_total"); + assertThat(m.getOriginalName()).isEqualTo("requests"); + } + + @Test + void builder_utf8NameWithCounterSuffix() { + MetricMetadata m = + MetricMetadata.builder().name("my.requests").counterSuffix(true).build(); + assertThat(m.getName()).isEqualTo("my.requests"); + assertThat(m.getExpositionBaseName()).isEqualTo("my.requests_total"); + assertThat(m.getPrometheusName()).isEqualTo("my_requests"); + assertThat(m.getExpositionBasePrometheusName()).isEqualTo("my_requests_total"); + } + + @Test + void builder_nonCounterExpositionBaseEqualsName() { + MetricMetadata m = MetricMetadata.builder().name("active_connections").build(); + assertThat(m.getExpositionBaseName()).isEqualTo(m.getName()); + } + + @Test + void builder_nameRequired() { + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> MetricMetadata.builder().help("help").build()) + .withMessage("name is required"); + } } From 3622cd6a8f5d77648093b0b163bb773a9221b37f Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 8 Jun 2026 08:58:00 +0000 Subject: [PATCH 2/7] style: apply google-java-format Signed-off-by: Gregor Zeitlinger --- .../prometheus/metrics/model/snapshots/MetricMetadataTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java index 0017b04bd..65148e55e 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java @@ -155,8 +155,7 @@ void builder_counterSuffixAndUnit() { @Test void builder_utf8NameWithCounterSuffix() { - MetricMetadata m = - MetricMetadata.builder().name("my.requests").counterSuffix(true).build(); + MetricMetadata m = MetricMetadata.builder().name("my.requests").counterSuffix(true).build(); assertThat(m.getName()).isEqualTo("my.requests"); assertThat(m.getExpositionBaseName()).isEqualTo("my.requests_total"); assertThat(m.getPrometheusName()).isEqualTo("my_requests"); From deedae3db9c113e9776370aa74d891ea6b65d981 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 8 Jun 2026 09:11:49 +0000 Subject: [PATCH 3/7] fix: suppress deprecation warning in MetricWithFixedMetadata Signed-off-by: Gregor Zeitlinger --- .../prometheus/metrics/core/metrics/MetricWithFixedMetadata.java | 1 + 1 file changed, 1 insertion(+) diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java index 9be797b0e..8d7e50b7b 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java @@ -27,6 +27,7 @@ public abstract class MetricWithFixedMetadata extends Metric { protected final MetricMetadata metadata; protected final String[] labelNames; + @SuppressWarnings("deprecation") protected MetricWithFixedMetadata(Builder builder) { super(builder); String name = makeName(builder.name, builder.unit); From 64044ab9e853543058e7a550af71b1b600888823 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 9 Jun 2026 11:26:04 +0000 Subject: [PATCH 4/7] fix: keep internal MetricMetadata builder hooks unstable Signed-off-by: Gregor Zeitlinger --- .../core/metrics/MetricWithFixedMetadata.java | 9 ++- .../model/snapshots/MetricMetadata.java | 58 ++++++++++++++----- .../snapshots/MetricMetadataSupport.java | 14 ++--- .../model/snapshots/MetricMetadataTest.java | 18 ++++++ 4 files changed, 74 insertions(+), 25 deletions(-) diff --git a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java index 8d7e50b7b..c639bf317 100644 --- a/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java +++ b/prometheus-metrics-core/src/main/java/io/prometheus/metrics/core/metrics/MetricWithFixedMetadata.java @@ -27,7 +27,6 @@ public abstract class MetricWithFixedMetadata extends Metric { protected final MetricMetadata metadata; protected final String[] labelNames; - @SuppressWarnings("deprecation") protected MetricWithFixedMetadata(Builder builder) { super(builder); String name = makeName(builder.name, builder.unit); @@ -37,7 +36,13 @@ protected MetricWithFixedMetadata(Builder builder) { String originalName = builder.originalName; String expositionBaseName = makeExpositionBaseName(originalName, builder.unit); this.metadata = - new MetricMetadata(name, expositionBaseName, originalName, builder.help, builder.unit); + MetricMetadata.builder() + .name(name) + .expositionBaseName(expositionBaseName) + .originalName(originalName) + .help(builder.help) + .unit(builder.unit) + .build(); this.labelNames = Arrays.copyOf(builder.labelNames, builder.labelNames.length); } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java index 3f9499b31..1a06711b1 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java @@ -93,9 +93,10 @@ public static Builder builder() { } /** Builder for {@link MetricMetadata}. */ - @StableApi public static final class Builder { @Nullable private String name; + @Nullable private String expositionBaseName; + @Nullable private String originalName; @Nullable private String help; @Nullable private Unit unit; private boolean counterSuffix; @@ -103,19 +104,35 @@ public static final class Builder { private Builder() {} /** Required. The base metric name (without type suffix like {@code _total}). */ + @StableApi public Builder name(String name) { this.name = name; + if (originalName == null) { + this.originalName = name; + } + return this; + } + + public Builder expositionBaseName(String expositionBaseName) { + this.expositionBaseName = expositionBaseName; + return this; + } + + public Builder originalName(String originalName) { + this.originalName = originalName; return this; } /** Optional. Human-readable description of the metric. */ - public Builder help(String help) { + @StableApi + public Builder help(@Nullable String help) { this.help = help; return this; } /** Optional. The unit of measurement. Appended to the name if not already present. */ - public Builder unit(Unit unit) { + @StableApi + public Builder unit(@Nullable Unit unit) { this.unit = unit; return this; } @@ -125,27 +142,29 @@ public Builder unit(Unit unit) { * this for counter metrics, especially UTF-8 names where the writer cannot infer it from the * snapshot type alone. */ + @StableApi public Builder counterSuffix(boolean counterSuffix) { this.counterSuffix = counterSuffix; return this; } /** Builds the {@link MetricMetadata}. Throws if {@code name} was not set. */ + @StableApi public MetricMetadata build() { if (name == null) { throw new IllegalArgumentException("name is required"); } - String baseName = name; - if (unit != null && !baseName.endsWith("_" + unit) && !baseName.endsWith("." + unit)) { - baseName = baseName + "_" + unit; - } - String expositionBaseName = baseName; + String baseName = appendUnitIfMissing(name, unit); + String originalName = this.originalName == null ? name : this.originalName; + String expositionBaseName = + appendUnitIfMissing( + this.expositionBaseName == null ? baseName : this.expositionBaseName, unit); if (counterSuffix && !expositionBaseName.endsWith("_total") && !expositionBaseName.endsWith(".total")) { expositionBaseName = expositionBaseName + "_total"; } - return new MetricMetadata(baseName, expositionBaseName, name, help, unit); + return new MetricMetadata(baseName, expositionBaseName, originalName, help, unit); } } @@ -254,6 +273,13 @@ public Unit getUnit() { return unit; } + private static String appendUnitIfMissing(String name, @Nullable Unit unit) { + if (unit != null && !name.endsWith("_" + unit) && !name.endsWith("." + unit)) { + return name + "_" + unit; + } + return name; + } + private void validate() { if (name == null) { throw new IllegalArgumentException("Missing required field: name is null"); @@ -285,13 +311,13 @@ private void validate() { } } - @SuppressWarnings("deprecation") MetricMetadata escape(EscapingScheme escapingScheme) { - return new MetricMetadata( - PrometheusNaming.escapeName(name, escapingScheme), - PrometheusNaming.escapeName(expositionBaseName, escapingScheme), - PrometheusNaming.escapeName(originalName, escapingScheme), - help, - unit); + return MetricMetadata.builder() + .name(PrometheusNaming.escapeName(name, escapingScheme)) + .expositionBaseName(PrometheusNaming.escapeName(expositionBaseName, escapingScheme)) + .originalName(PrometheusNaming.escapeName(originalName, escapingScheme)) + .help(help) + .unit(unit) + .build(); } } diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadataSupport.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadataSupport.java index 96083416f..978f56d43 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadataSupport.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadataSupport.java @@ -18,7 +18,6 @@ static MetricMetadata infoMetadata(String name, @Nullable String help) { return typedMetadata(name, help, null, "_info", ".info"); } - @SuppressWarnings("deprecation") private static MetricMetadata typedMetadata( String originalName, @Nullable String help, @@ -26,12 +25,13 @@ private static MetricMetadata typedMetadata( String suffix, String dotSuffix) { String baseName = stripSuffix(originalName, suffix, dotSuffix); - return new MetricMetadata( - appendUnitIfMissing(baseName, unit), - appendUnitIfMissing(originalName, unit), - originalName, - help, - unit); + return MetricMetadata.builder() + .name(appendUnitIfMissing(baseName, unit)) + .expositionBaseName(appendUnitIfMissing(originalName, unit)) + .originalName(originalName) + .help(help) + .unit(unit) + .build(); } private static String appendUnitIfMissing(String name, @Nullable Unit unit) { diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java index 65148e55e..f05311970 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java @@ -162,6 +162,24 @@ void builder_utf8NameWithCounterSuffix() { assertThat(m.getExpositionBasePrometheusName()).isEqualTo("my_requests_total"); } + + @Test + void builder_customOriginalAndExpositionBaseName() { + MetricMetadata m = + MetricMetadata.builder() + .name("requests_bytes") + .expositionBaseName("requests_total_bytes") + .originalName("requests_total") + .help("help") + .unit(Unit.BYTES) + .build(); + assertThat(m.getName()).isEqualTo("requests_bytes"); + assertThat(m.getExpositionBaseName()).isEqualTo("requests_total_bytes"); + assertThat(m.getOriginalName()).isEqualTo("requests_total"); + assertThat(m.getHelp()).isEqualTo("help"); + assertThat(m.getUnit()).isEqualTo(Unit.BYTES); + } + @Test void builder_nonCounterExpositionBaseEqualsName() { MetricMetadata m = MetricMetadata.builder().name("active_connections").build(); From 47665292b4a35913b6c6fdc285cfd05a9f5d798b Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 9 Jun 2026 11:26:14 +0000 Subject: [PATCH 5/7] style: apply google-java-format Signed-off-by: Gregor Zeitlinger --- .../prometheus/metrics/model/snapshots/MetricMetadataTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java index f05311970..3d83887d5 100644 --- a/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java +++ b/prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/MetricMetadataTest.java @@ -162,7 +162,6 @@ void builder_utf8NameWithCounterSuffix() { assertThat(m.getExpositionBasePrometheusName()).isEqualTo("my_requests_total"); } - @Test void builder_customOriginalAndExpositionBaseName() { MetricMetadata m = From 938e4f27226b07134cbe75a3b202340bebb236e9 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 9 Jun 2026 12:18:54 +0000 Subject: [PATCH 6/7] docs: clarify internal MetricMetadata builder hooks Signed-off-by: Gregor Zeitlinger --- .../metrics/model/snapshots/MetricMetadata.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java index 1a06711b1..aeb37c474 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java @@ -113,11 +113,21 @@ public Builder name(String name) { return this; } + /** + * Internal use only. Not part of the stable API. + * + *

Allows internal callers to preserve a separate exposition base name. + */ public Builder expositionBaseName(String expositionBaseName) { this.expositionBaseName = expositionBaseName; return this; } + /** + * Internal use only. Not part of the stable API. + * + *

Allows internal callers to preserve the raw name before normalization. + */ public Builder originalName(String originalName) { this.originalName = originalName; return this; From 0d7e8a9e7e042612bab760e37bafa5363cfb6574 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 9 Jun 2026 17:44:41 +0000 Subject: [PATCH 7/7] fix: narrow stable MetricMetadata api annotations Signed-off-by: Gregor Zeitlinger --- .../metrics/model/snapshots/MetricMetadata.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java index aeb37c474..8fad68c85 100644 --- a/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java +++ b/prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/snapshots/MetricMetadata.java @@ -5,7 +5,6 @@ import javax.annotation.Nullable; /** Immutable container for metric metadata: name, help, unit. */ -@StableApi public final class MetricMetadata { /** @@ -51,11 +50,13 @@ public final class MetricMetadata { @Nullable private final Unit unit; /** See {@link #MetricMetadata(String, String, Unit)} */ + @StableApi public MetricMetadata(String name) { this(name, null, null); } /** See {@link #MetricMetadata(String, String, Unit)} */ + @StableApi public MetricMetadata(String name, String help) { this(name, help, null); } @@ -69,6 +70,7 @@ public MetricMetadata(String name, String help) { * @param help optional. May be {@code null}. * @param unit optional. May be {@code null}. */ + @StableApi public MetricMetadata(String name, @Nullable String help, @Nullable Unit unit) { this(name, name, help, unit); } @@ -189,6 +191,7 @@ public MetricMetadata build() { * @param unit optional. May be {@code null}. * @deprecated Use {@link #builder()} instead. */ + @StableApi @Deprecated public MetricMetadata( String name, String expositionBaseName, @Nullable String help, @Nullable Unit unit) { @@ -206,6 +209,7 @@ public MetricMetadata( * @param unit optional. May be {@code null}. * @deprecated Use {@link #builder()} instead. */ + @StableApi @Deprecated public MetricMetadata( String name, @@ -230,6 +234,7 @@ public MetricMetadata( *

The name may contain any Unicode chars. Use {@link #getPrometheusName()} to get the name in * legacy Prometheus format, i.e. with all dots and all invalid chars replaced by underscores. */ + @StableApi public String getName() { return name; } @@ -239,6 +244,7 @@ public String getName() { * *

This is used by Prometheus exposition formats. */ + @StableApi public String getPrometheusName() { return prometheusName; } @@ -248,6 +254,7 @@ public String getPrometheusName() { * called {@code Counter.builder().name("req").unit(BYTES)}, this returns "req" while {@link * #getName()} returns "req_bytes" and {@link #getExpositionBaseName()} returns "req_bytes". */ + @StableApi public String getOriginalName() { return originalName; } @@ -257,6 +264,7 @@ public String getOriginalName() { * if the user called {@code Counter.builder().name("events_total")}, this returns "events_total" * while {@link #getName()} returns "events". */ + @StableApi public String getExpositionBaseName() { return expositionBaseName; } @@ -265,19 +273,23 @@ public String getExpositionBaseName() { * Same as {@link #getExpositionBaseName()} but with all invalid characters and dots replaced by * underscores. */ + @StableApi public String getExpositionBasePrometheusName() { return expositionBasePrometheusName; } + @StableApi @Nullable public String getHelp() { return help; } + @StableApi public boolean hasUnit() { return unit != null; } + @StableApi @Nullable public Unit getUnit() { return unit;