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.
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 =true
For the minimal OM2 experience (names as provided, no suffix appending):
io.prometheus.openmetrics2.enabled =true
Flag
Description
Spec reference
enabled
Activate the OM2 writer. Required — feature flags below have no effect without it. Set implicitly by enableOpenMetrics2()
—
contentNegotiation
Gate 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
compositeValues
Single-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
exemplarCompliance
Exemplar timestamps always emitted (MUST in OM2), 128-char LabelSet hard limit removed
Exemplar
nativeHistograms
Exponential 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 (#1989)
│
└── PR 7: nativeHistograms (#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 ExpositionFormatWriter
Wired 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 implicitly
PR 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 expositionBaseName
Counter/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 them
Stacked 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 OM1
Spec: Content Type
PR 6: CompositeValue + start timestamp + exemplar compliance (#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 count/sum (not gcount/gsum)
_created lines replaced by inline st@ start timestamp
_count, _sum, _bucket suffixed lines no longer emitted for these types
Reserved 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:
Exemplar timestamps always emitted (OM2: MUST , was MAY in OM1)
128-char LabelSet hard limit removed
PR 7: Native histograms (#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:
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.
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 (#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]}count/sum(notgcount/gsum)_createdlines replaced by inlinest@start timestamp_count,_sum,_bucketsuffixed lines no longer emitted for these types_to__exemplarCompliance:PR 7: Native histograms (#1990)
nativeHistograms:{count:X,sum:X,schema:N,zero_threshold:F,zero_count:X,positive_spans:[...],positive_buckets:[...]}Notes