The OpenMetrics 2.0 spec is nearing completion. We should start implementing experimental support to validate the design and provide early feedback to the spec authors.
All features are behind opt-in flags (off by default) so there's no risk to existing users. The spec is still in draft, so the API is explicitly experimental and subject to change.
Status Implemented:
Remaining:
Flag design Top-level gate + individual feature flags. The top-level gate (enableOpenMetrics2())
sets enabled=true implicitly and enables the sub-flags. Each sub-flag
independently controls one OM2 feature.
Java builder API:
PrometheusProperties config = PrometheusProperties .builder ()
.enableOpenMetrics2 (om2 -> om2
.contentNegotiation (true ) // version=2.0.0 content type
.compositeValues (true ) // single-line histogram/summary + st@
.exemplarCompliance (true ) // mandatory timestamps, no size limit
.nativeHistograms (true ) // exponential buckets
)
.build ();
// Or enable everything at once PrometheusProperties config = PrometheusProperties .builder ()
.enableOpenMetrics2 (om2 -> om2 .enableAll ())
.build ();Properties equivalent:
io.prometheus.openmetrics2.enabled =true
io.prometheus.openmetrics2.content_negotiation =true
io.prometheus.openmetrics2.composite_values =true
io.prometheus.openmetrics2.exemplar_compliance =true
io.prometheus.openmetrics2.native_histograms =trueFor the minimal OM2 experience (names as provided, no suffix appending):
io.prometheus.openmetrics2.enabled =trueFlag Description Spec reference enabledActivate the OM2 writer. Required — feature flags below have no effect without it. Set implicitly by enableOpenMetrics2() — contentNegotiationGate OM2 features behind content negotiation: only apply them when the scraper requests version=2.0.0, and return OM1 format when the scraper requests OM1. Without this flag, OM2 features are applied even if the scraper requests OM1 Content Type compositeValuesSingle-line Histogram/Summary/GaugeHistogram with inline st@ start timestamp. Also: reserved label prefix __, Histogram Count/Sum always present, bucket values may be float CompositeValue , Overall Structure exemplarComplianceExemplar timestamps always emitted (MUST in OM2), 128-char LabelSet hard limit removed Exemplar nativeHistogramsExponential buckets with schema, zero threshold/count, pos/neg spans Native Buckets
All flags default to false.
Suffix handling (_total, unit suffixes) is not an OM2 config flag — it is handled
at scrape time by each format writer. See #1941 for the design.
PR structure PR 1: Flag infrastructure (#1939) ✅
│
└── PR 2: OM2 writer skeleton (#1951) ✅
│
├── PR 3: enabled flag (#1953) ✅
│ │
│ ├── PR 4: Suffix handling:
│ │ ├── PR 4a: Core + OM1 writers (#1955) ✅
│ │ │ ├── PR 4b: OTel preserve_names (#1956) ✅
│ │ │ └── PR 4c: OM2 writer no-suffix (#1957) ✅
│ │ │
│ │
│ └── PR 8: Documentation (#1954)
│
├── PR 5: contentNegotiation (#1986) ✅
│
├── PR 6: compositeValues + exemplarCompliance (#1991, closes #1989) ✅
│
└── PR 7: nativeHistograms (#2042, closes #1990) ✅
PR 1: Flag infrastructure (#1939 ) ✅ Add enableOpenMetrics2() to PrometheusProperties.Builder Add OpenMetrics2Properties class with all flag fields (defaulting to false) Wire up properties parsing for io.prometheus.openmetrics2.* No behavioral changes — just the config plumbing PR 2: OM2 writer skeleton (#1951 ) ✅ OpenMetrics2TextFormatWriter implementing ExpositionFormatWriterWired into ExpositionFormats: when OM2 is enabled, selected for OpenMetrics requests (masquerades as OM1 for testability with current Prometheus) Takes OpenMetrics2Properties from feat: Add OpenMetrics2 configuration support #1939 in builder PR 3: enabled flag (#1953 ) ✅ Add io.prometheus.openmetrics2.enabled as the explicit gate for OM2 writer selection Feature flags alone no longer activate OM2 — enabled=true is required enableOpenMetrics2() programmatic configurator sets enabled=true implicitlyPR 4a: Core + OM1 writers (#1955 ) ✅ Move suffix handling to scrape time — OM1 smart-appends _total/_info/unit suffixes (skips if already present) MetricMetadata stores originalName and expositionBaseNameCounter/Info builders no longer strip _total/_info at creation time Remove all reserved metric name suffixes from PrometheusNaming Cross-format collision detection in PrometheusRegistry See Move suffix handling from creation time to scrape time #1942 for design PR 4b: OTel preserve_names (#1956 ) ✅ Add preserve_names config to ExporterOpenTelemetryProperties Legacy path (default): strips _total + unit suffix preserve_names=true: passes names through exactly as the user wrote themStacked on feat: move suffix handling to scrape time #1955 PR 4c: OM2 writer no-suffix (#1957 ) ✅ PR 8: Documentation (#1954 ) Document OM2 preview features and configuration Document OTel preserve_names flag PR 5: Content negotiation (#1986 ) ✅ contentNegotiation: only return OM2 format when the scraper explicitly requests version=2.0.0. Without this flag, OM2 features are applied even if the scraper requests OM1Spec: Content Type PR 6: CompositeValue + start timestamp + exemplar compliance (#1991 , closes #1989 ) ✅ compositeValues:
Histogram as single line: foo {count:17,sum:324789.3,bucket:[0.1:8,0.25:10,0.5:11,1.0:14,+Inf:17]} 1.0 st@0.5 Summary as single line: bar {count:17,sum:324789.3,quantile:[0.95:123.7,0.99:150.0]} GaugeHistogram as single line with gcount/gsum _created lines replaced by inline st@ start timestamp_count, _sum, _bucket suffixed lines no longer emitted for these typesReserved label prefix changed from _ to __ Histogram/GaugeHistogram Count and Sum always present (MUST, was SHOULD for Sum) Histogram bucket values may be float (SHOULD be integer, was MUST) Spec: CompositeValue , Histogram , Summary exemplarCompliance:
Exemplars without timestamps are dropped so emitted OM2 exemplars always satisfy the timestamp requirement 128-char LabelSet hard limit removed PR 7: Native histograms (#2042 , closes #1990 ) ✅ nativeHistograms:
Exponential bucket model: schema (-4 to 8), zero threshold/count, positive/negative spans and buckets Text serialization: {count:X,sum:X,schema:N,zero_threshold:F,zero_count:X,positive_spans:[...],positive_buckets:[...]} Can coexist with classic buckets on separate lines (native line MUST come first) GaugeHistogram also supports native buckets (same syntax) Multiple exemplars per native histogram sample Spec: Native Buckets , Histogram No known blockers:
Next steps Finish docs in OM2: docs: Document OpenMetrics 2.0 preview and OTel preserve_names flag #1954 now that the implemented OM2 surface is stable enough to describe precisely. After OM2: docs: Document OpenMetrics 2.0 preview and OTel preserve_names flag #1954 lands, do one pass over Experimental OpenMetrics 2.0 support #1912 for any spec drift and then close the umbrella issue. Notes UTF-8 metric/label names are already supported (shipped in a previous release) The OM2 spec is still draft — some details may change, but no known blockers for the features covered here See also: OM 2.0 upgrade guide for SDK maintainers
The OpenMetrics 2.0 spec is nearing completion. We should start implementing experimental support to validate the design and provide early feedback to the spec authors.
All features are behind opt-in flags (off by default) so there's no risk to existing users. The spec is still in draft, so the API is explicitly experimental and subject to change.
Status
Implemented:
Remaining:
Flag design
Top-level gate + individual feature flags. The top-level gate (
enableOpenMetrics2())sets
enabled=trueimplicitly and enables the sub-flags. Each sub-flagindependently controls one OM2 feature.
Java builder API:
Properties equivalent:
For the minimal OM2 experience (names as provided, no suffix appending):
io.prometheus.openmetrics2.enabled=trueenabledenableOpenMetrics2()contentNegotiationversion=2.0.0, and return OM1 format when the scraper requests OM1. Without this flag, OM2 features are applied even if the scraper requests OM1compositeValuesst@start timestamp. Also: reserved label prefix__, Histogram Count/Sum always present, bucket values may be floatexemplarCompliancenativeHistogramsAll flags default to
false.Suffix handling (
_total, unit suffixes) is not an OM2 config flag — it is handledat scrape time by each format writer. See #1941 for the design.
PR structure
PR 1: Flag infrastructure (#1939) ✅
enableOpenMetrics2()toPrometheusProperties.BuilderOpenMetrics2Propertiesclass with all flag fields (defaulting tofalse)io.prometheus.openmetrics2.*PR 2: OM2 writer skeleton (#1951) ✅
OpenMetrics2TextFormatWriterimplementingExpositionFormatWriterExpositionFormats: when OM2 is enabled, selected for OpenMetrics requests (masquerades as OM1 for testability with current Prometheus)OpenMetrics2Propertiesfrom feat: Add OpenMetrics2 configuration support #1939 in builderPR 3: enabled flag (#1953) ✅
io.prometheus.openmetrics2.enabledas the explicit gate for OM2 writer selectionenabled=trueis requiredenableOpenMetrics2()programmatic configurator setsenabled=trueimplicitlyPR 4a: Core + OM1 writers (#1955) ✅
_total/_info/unit suffixes (skips if already present)MetricMetadatastoresoriginalNameandexpositionBaseName_total/_infoat creation timePrometheusNamingPrometheusRegistryPR 4b: OTel preserve_names (#1956) ✅
preserve_namesconfig toExporterOpenTelemetryProperties_total+ unit suffixpreserve_names=true: passes names through exactly as the user wrote themPR 4c: OM2 writer no-suffix (#1957) ✅
expositionBaseNamedirectly — no_totalor unit suffix appending_infosuffix enforced per OM2 spec (MUST)PR 8: Documentation (#1954)
preserve_namesflagPR 5: Content negotiation (#1986) ✅
contentNegotiation: only return OM2 format when the scraper explicitly requestsversion=2.0.0. Without this flag, OM2 features are applied even if the scraper requests OM1PR 6: CompositeValue + start timestamp + exemplar compliance (#1991, closes #1989) ✅
compositeValues:foo {count:17,sum:324789.3,bucket:[0.1:8,0.25:10,0.5:11,1.0:14,+Inf:17]} 1.0 st@0.5bar {count:17,sum:324789.3,quantile:[0.95:123.7,0.99:150.0]}gcount/gsum_createdlines replaced by inlinest@start timestamp_count,_sum,_bucketsuffixed lines no longer emitted for these types_to__exemplarCompliance:PR 7: Native histograms (#2042, closes #1990) ✅
nativeHistograms:{count:X,sum:X,schema:N,zero_threshold:F,zero_count:X,positive_spans:[...],positive_buckets:[...]}Next steps
Notes