From 4559fdf00c161085de08b62f8b6dc082f34c12ff Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 10 Apr 2026 17:56:39 +0000 Subject: [PATCH 1/7] Add InstrumentationDefaults helper to declarative-config-bridge Provides a utility for distributions to define instrumentation property defaults once and have them work with both traditional property-based configuration (otel.instrumentation.*) and declarative configuration (YAML model under instrumentation/development.java). Extracted from grafana/grafana-opentelemetry-java#1226. Signed-off-by: Gregor Zeitlinger --- declarative-config-bridge/build.gradle.kts | 1 + .../bridge/InstrumentationDefaults.java | 108 ++++++++++++++++++ .../bridge/InstrumentationDefaultsTest.java | 71 ++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 declarative-config-bridge/src/main/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaults.java create mode 100644 declarative-config-bridge/src/test/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaultsTest.java diff --git a/declarative-config-bridge/build.gradle.kts b/declarative-config-bridge/build.gradle.kts index b5ac9ec9c1c3..7da52f11f4fd 100644 --- a/declarative-config-bridge/build.gradle.kts +++ b/declarative-config-bridge/build.gradle.kts @@ -8,6 +8,7 @@ group = "io.opentelemetry.instrumentation" dependencies { compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + compileOnly("io.opentelemetry:opentelemetry-sdk-extension-incubator") implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") implementation("io.opentelemetry:opentelemetry-api-incubator") diff --git a/declarative-config-bridge/src/main/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaults.java b/declarative-config-bridge/src/main/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaults.java new file mode 100644 index 000000000000..0153a03cb0dd --- /dev/null +++ b/declarative-config-bridge/src/main/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaults.java @@ -0,0 +1,108 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.config.bridge; + +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalInstrumentationModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalLanguageSpecificInstrumentationModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalLanguageSpecificInstrumentationPropertyModel; +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Defines instrumentation defaults that work with both traditional property-based configuration and + * declarative configuration. + * + *

Usage: + * + *

{@code
+ * InstrumentationDefaults defaults = new InstrumentationDefaults();
+ * defaults.setDefault("micrometer", "base_time_unit", "s");
+ * defaults.setDefault("log4j_appender", "experimental_log_attributes", "true");
+ *
+ * // Declarative config mode: inject into model
+ * customizer.addModelCustomizer(model -> defaults.applyToModel(model));
+ *
+ * // Traditional mode: translate to ConfigProperties
+ * autoConfiguration.addPropertiesSupplier(defaults::toConfigProperties);
+ * }
+ */ +public final class InstrumentationDefaults { + + private final Map> instrumentations = new LinkedHashMap<>(); + + /** + * Sets a default value for an instrumentation property. Keys use underscore notation (e.g. {@code + * base_time_unit}); they are translated to hyphen notation when producing property keys. + * + * @return {@code this} for chaining + */ + public InstrumentationDefaults setDefault(String instrumentation, String key, String value) { + instrumentations.computeIfAbsent(instrumentation, k -> new LinkedHashMap<>()).put(key, value); + return this; + } + + /** Translates defaults to {@code otel.instrumentation.*} keys for auto-configuration. */ + public Map toConfigProperties() { + HashMap map = new HashMap<>(); + instrumentations.forEach( + (instrumentation, properties) -> + properties.forEach( + (key, value) -> + map.put( + "otel.instrumentation." + + instrumentation.replace('_', '-') + + "." + + key.replace('_', '-'), + value))); + return map; + } + + /** + * Applies defaults to the declarative configuration model under {@code + * instrumentation/development.java}. Existing values in the model take precedence; defaults are + * only set for properties not already present. + */ + public OpenTelemetryConfigurationModel applyToModel(OpenTelemetryConfigurationModel model) { + if (instrumentations.isEmpty()) { + return model; + } + + ExperimentalInstrumentationModel instrumentation = model.getInstrumentationDevelopment(); + if (instrumentation == null) { + instrumentation = new ExperimentalInstrumentationModel(); + model.withInstrumentationDevelopment(instrumentation); + } + ExperimentalLanguageSpecificInstrumentationModel java = instrumentation.getJava(); + if (java == null) { + java = new ExperimentalLanguageSpecificInstrumentationModel(); + instrumentation.withJava(java); + } + + Map props = + java.getAdditionalProperties(); + + for (Map.Entry> entry : instrumentations.entrySet()) { + String name = entry.getKey(); + Map defaults = entry.getValue(); + + ExperimentalLanguageSpecificInstrumentationPropertyModel propModel = props.get(name); + if (propModel == null) { + propModel = new ExperimentalLanguageSpecificInstrumentationPropertyModel(); + props.put(name, propModel); + } + + for (Map.Entry defaultEntry : defaults.entrySet()) { + propModel + .getAdditionalProperties() + .putIfAbsent(defaultEntry.getKey(), defaultEntry.getValue()); + } + } + + return model; + } +} diff --git a/declarative-config-bridge/src/test/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaultsTest.java b/declarative-config-bridge/src/test/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaultsTest.java new file mode 100644 index 000000000000..7e89f17aafab --- /dev/null +++ b/declarative-config-bridge/src/test/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaultsTest.java @@ -0,0 +1,71 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.config.bridge; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class InstrumentationDefaultsTest { + + @Test + void toConfigProperties() { + InstrumentationDefaults defaults = new InstrumentationDefaults(); + defaults.setDefault("micrometer", "base_time_unit", "s"); + defaults.setDefault("log4j_appender", "experimental_log_attributes", "true"); + + Map props = defaults.toConfigProperties(); + + assertThat(props) + .containsEntry("otel.instrumentation.micrometer.base-time-unit", "s") + .containsEntry("otel.instrumentation.log4j-appender.experimental-log-attributes", "true") + .hasSize(2); + } + + @Test + void applyToModel() { + InstrumentationDefaults defaults = new InstrumentationDefaults(); + defaults.setDefault("micrometer", "base_time_unit", "s"); + + OpenTelemetryConfigurationModel model = new OpenTelemetryConfigurationModel(); + defaults.applyToModel(model); + + assertThat( + model + .getInstrumentationDevelopment() + .getJava() + .getAdditionalProperties() + .get("micrometer") + .getAdditionalProperties()) + .containsEntry("base_time_unit", "s"); + } + + @Test + void applyToModelDoesNotOverrideExisting() { + InstrumentationDefaults defaults = new InstrumentationDefaults(); + defaults.setDefault("micrometer", "base_time_unit", "s"); + + // Pre-populate model with a different value + OpenTelemetryConfigurationModel model = new OpenTelemetryConfigurationModel(); + new InstrumentationDefaults() + .setDefault("micrometer", "base_time_unit", "ms") + .applyToModel(model); + + // Apply defaults — should not override + defaults.applyToModel(model); + + assertThat( + model + .getInstrumentationDevelopment() + .getJava() + .getAdditionalProperties() + .get("micrometer") + .getAdditionalProperties()) + .containsEntry("base_time_unit", "ms"); + } +} From 0572994339b673ad4f48fe48558e54a18c3709bc Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 13 Apr 2026 13:45:28 +0000 Subject: [PATCH 2/7] fix: add @CanIgnoreReturnValue to InstrumentationDefaults.setDefault Signed-off-by: Gregor Zeitlinger --- .../instrumentation/config/bridge/InstrumentationDefaults.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/declarative-config-bridge/src/main/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaults.java b/declarative-config-bridge/src/main/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaults.java index 0153a03cb0dd..bdb0ce02b01f 100644 --- a/declarative-config-bridge/src/main/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaults.java +++ b/declarative-config-bridge/src/main/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaults.java @@ -5,6 +5,7 @@ package io.opentelemetry.instrumentation.config.bridge; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalInstrumentationModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalLanguageSpecificInstrumentationModel; import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.ExperimentalLanguageSpecificInstrumentationPropertyModel; @@ -41,6 +42,7 @@ public final class InstrumentationDefaults { * * @return {@code this} for chaining */ + @CanIgnoreReturnValue public InstrumentationDefaults setDefault(String instrumentation, String key, String value) { instrumentations.computeIfAbsent(instrumentation, k -> new LinkedHashMap<>()).put(key, value); return this; From deef7d881e5c27a14ca40765e32e6871e2e79389 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 13 Apr 2026 14:49:18 +0000 Subject: [PATCH 3/7] fix: add @CanIgnoreReturnValue to applyToModel Signed-off-by: Gregor Zeitlinger --- .../instrumentation/config/bridge/InstrumentationDefaults.java | 1 + 1 file changed, 1 insertion(+) diff --git a/declarative-config-bridge/src/main/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaults.java b/declarative-config-bridge/src/main/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaults.java index bdb0ce02b01f..9f5e70811482 100644 --- a/declarative-config-bridge/src/main/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaults.java +++ b/declarative-config-bridge/src/main/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaults.java @@ -69,6 +69,7 @@ public Map toConfigProperties() { * instrumentation/development.java}. Existing values in the model take precedence; defaults are * only set for properties not already present. */ + @CanIgnoreReturnValue public OpenTelemetryConfigurationModel applyToModel(OpenTelemetryConfigurationModel model) { if (instrumentations.isEmpty()) { return model; From 97ab9a4c917586710aecae5c67b612e42c2716f3 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 13 Apr 2026 17:32:15 +0000 Subject: [PATCH 4/7] docs: add InstrumentationDefaults usage to declarative-config-bridge README Signed-off-by: Gregor Zeitlinger --- declarative-config-bridge/README.md | 49 +++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/declarative-config-bridge/README.md b/declarative-config-bridge/README.md index 0e6b7154bb15..c6210764ffff 100644 --- a/declarative-config-bridge/README.md +++ b/declarative-config-bridge/README.md @@ -82,3 +82,52 @@ public class InferredSpansComponentProvider implements ComponentProvider { } } ``` + +## InstrumentationDefaults + +`InstrumentationDefaults` lets distribution authors define instrumentation property defaults once +and have them work in both configuration modes — traditional auto-configuration and declarative +configuration. + +### Usage + +```java +InstrumentationDefaults defaults = new InstrumentationDefaults(); +defaults.setDefault("micrometer", "base_time_unit", "s"); +defaults.setDefault("log4j_appender", "experimental_log_attributes", "true"); +``` + +Keys use underscore notation (matching the declarative config model). They are translated to +hyphen notation (`otel.instrumentation..`) when producing system property keys. + +### Auto-configuration (traditional) + +Register the defaults as a properties supplier: + +```java +@AutoService(AutoConfigurationCustomizerProvider.class) +public class MyDistroAutoConfig implements AutoConfigurationCustomizerProvider { + @Override + public void customize(AutoConfigurationCustomizer autoConfiguration) { + autoConfiguration.addPropertiesSupplier(defaults::toConfigProperties); + } +} +``` + +### Declarative configuration + +Register the defaults as a model customizer: + +```java +@AutoService(DeclarativeConfigurationCustomizerProvider.class) +public class MyDistroDeclarativeConfig implements DeclarativeConfigurationCustomizerProvider { + @Override + public void customize(DeclarativeConfigurationCustomizer customizer) { + customizer.addModelCustomizer(model -> defaults.applyToModel(model)); + } +} +``` + +Defaults are injected under `instrumentation/development.java` in the model. Explicit user +configuration always takes precedence — defaults are only applied for properties not already +present. From 1e43af3dabaf200a98e242e825bca33c86b91ba4 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 17 Apr 2026 12:48:22 +0000 Subject: [PATCH 5/7] docs(declarative-config-bridge): restructure InstrumentationDefaults readme to match DC API pattern Signed-off-by: Gregor Zeitlinger --- declarative-config-bridge/README.md | 33 ++++++++++++++++++----------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/declarative-config-bridge/README.md b/declarative-config-bridge/README.md index c6210764ffff..a70ec933dcc7 100644 --- a/declarative-config-bridge/README.md +++ b/declarative-config-bridge/README.md @@ -86,10 +86,8 @@ public class InferredSpansComponentProvider implements ComponentProvider { ## InstrumentationDefaults `InstrumentationDefaults` lets distribution authors define instrumentation property defaults once -and have them work in both configuration modes — traditional auto-configuration and declarative -configuration. - -### Usage +and have them work in both configuration modes. +First, there is a single defaults object that is unaware of the source of the configuration: ```java InstrumentationDefaults defaults = new InstrumentationDefaults(); @@ -100,9 +98,8 @@ defaults.setDefault("log4j_appender", "experimental_log_attributes", "true"); Keys use underscore notation (matching the declarative config model). They are translated to hyphen notation (`otel.instrumentation..`) when producing system property keys. -### Auto-configuration (traditional) - -Register the defaults as a properties supplier: +The auto configuration **without declarative config** registers the defaults as a properties +supplier, translating them to `otel.instrumentation.*` keys: ```java @AutoService(AutoConfigurationCustomizerProvider.class) @@ -114,9 +111,22 @@ public class MyDistroAutoConfig implements AutoConfigurationCustomizerProvider { } ``` -### Declarative configuration +The auto configuration **with declarative config** registers the defaults as a model customizer, +injecting them under `instrumentation/development.java`. + +Let's first look at the yaml file that the defaults effectively merge into: + +```yaml +file_format: 1.0 +instrumentation/development: + java: + micrometer: + base_time_unit: s + log4j_appender: + experimental_log_attributes: "true" +``` -Register the defaults as a model customizer: +And now the customizer that applies the defaults to the model: ```java @AutoService(DeclarativeConfigurationCustomizerProvider.class) @@ -128,6 +138,5 @@ public class MyDistroDeclarativeConfig implements DeclarativeConfigurationCustom } ``` -Defaults are injected under `instrumentation/development.java` in the model. Explicit user -configuration always takes precedence — defaults are only applied for properties not already -present. +Explicit user configuration always takes precedence — defaults are only applied for properties not +already present (`putIfAbsent`). From 425d9c20c9fa27b43ad090d3490eea0f0b234531 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Sat, 18 Apr 2026 08:27:34 +0000 Subject: [PATCH 6/7] refactor(declarative-config-bridge): nest InstrumentationDefaults API Replace vararg setDefault(instr, key, value) with nested defaults.get(name).setDefault(key, value). Mirrors DeclarativeConfigProperties.getStructured(name).getString(key) on the read side. README notes the symmetry. Signed-off-by: Gregor Zeitlinger --- declarative-config-bridge/README.md | 8 ++- .../bridge/InstrumentationDefaults.java | 49 +++++++++++++------ .../bridge/InstrumentationDefaultsTest.java | 19 ++++--- 3 files changed, 50 insertions(+), 26 deletions(-) diff --git a/declarative-config-bridge/README.md b/declarative-config-bridge/README.md index a70ec933dcc7..973f89961808 100644 --- a/declarative-config-bridge/README.md +++ b/declarative-config-bridge/README.md @@ -91,10 +91,14 @@ First, there is a single defaults object that is unaware of the source of the co ```java InstrumentationDefaults defaults = new InstrumentationDefaults(); -defaults.setDefault("micrometer", "base_time_unit", "s"); -defaults.setDefault("log4j_appender", "experimental_log_attributes", "true"); +defaults.get("micrometer").setDefault("base_time_unit", "s"); +defaults.get("log4j_appender").setDefault("experimental_log_attributes", "true"); ``` +Navigation mirrors `DeclarativeConfigProperties` — reading uses +`config.getStructured("micrometer").getString("base_time_unit")`; writing defaults uses +`defaults.get("micrometer").setDefault("base_time_unit", "s")`. + Keys use underscore notation (matching the declarative config model). They are translated to hyphen notation (`otel.instrumentation..`) when producing system property keys. diff --git a/declarative-config-bridge/src/main/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaults.java b/declarative-config-bridge/src/main/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaults.java index 9f5e70811482..516d44fc602c 100644 --- a/declarative-config-bridge/src/main/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaults.java +++ b/declarative-config-bridge/src/main/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaults.java @@ -18,12 +18,16 @@ * Defines instrumentation defaults that work with both traditional property-based configuration and * declarative configuration. * + *

Navigation mirrors {@link io.opentelemetry.api.incubator.config.DeclarativeConfigProperties}: + * read-side uses {@code config.getStructured(name).getString(key)}; write-side uses {@code + * defaults.get(name).setDefault(key, value)}. + * *

Usage: * *

{@code
  * InstrumentationDefaults defaults = new InstrumentationDefaults();
- * defaults.setDefault("micrometer", "base_time_unit", "s");
- * defaults.setDefault("log4j_appender", "experimental_log_attributes", "true");
+ * defaults.get("micrometer").setDefault("base_time_unit", "s");
+ * defaults.get("log4j_appender").setDefault("experimental_log_attributes", "true");
  *
  * // Declarative config mode: inject into model
  * customizer.addModelCustomizer(model -> defaults.applyToModel(model));
@@ -34,18 +38,14 @@
  */
 public final class InstrumentationDefaults {
 
-  private final Map> instrumentations = new LinkedHashMap<>();
+  private final Map instrumentations = new LinkedHashMap<>();
 
   /**
-   * Sets a default value for an instrumentation property. Keys use underscore notation (e.g. {@code
-   * base_time_unit}); they are translated to hyphen notation when producing property keys.
-   *
-   * @return {@code this} for chaining
+   * Returns the defaults builder for the given instrumentation, creating it if absent. Mirrors
+   * {@code DeclarativeConfigProperties.getStructured(name)} on the read side.
    */
-  @CanIgnoreReturnValue
-  public InstrumentationDefaults setDefault(String instrumentation, String key, String value) {
-    instrumentations.computeIfAbsent(instrumentation, k -> new LinkedHashMap<>()).put(key, value);
-    return this;
+  public InstrumentationProperties get(String instrumentation) {
+    return instrumentations.computeIfAbsent(instrumentation, k -> new InstrumentationProperties());
   }
 
   /** Translates defaults to {@code otel.instrumentation.*} keys for auto-configuration. */
@@ -53,7 +53,7 @@ public Map toConfigProperties() {
     HashMap map = new HashMap<>();
     instrumentations.forEach(
         (instrumentation, properties) ->
-            properties.forEach(
+            properties.properties.forEach(
                 (key, value) ->
                     map.put(
                         "otel.instrumentation."
@@ -89,9 +89,9 @@ public OpenTelemetryConfigurationModel applyToModel(OpenTelemetryConfigurationMo
     Map props =
         java.getAdditionalProperties();
 
-    for (Map.Entry> entry : instrumentations.entrySet()) {
+    for (Map.Entry entry : instrumentations.entrySet()) {
       String name = entry.getKey();
-      Map defaults = entry.getValue();
+      Map defaults = entry.getValue().properties;
 
       ExperimentalLanguageSpecificInstrumentationPropertyModel propModel = props.get(name);
       if (propModel == null) {
@@ -108,4 +108,25 @@ public OpenTelemetryConfigurationModel applyToModel(OpenTelemetryConfigurationMo
 
     return model;
   }
+
+  /** Defaults for a single instrumentation. Keys use underscore notation. */
+  public static final class InstrumentationProperties {
+
+    private final Map properties = new LinkedHashMap<>();
+
+    private InstrumentationProperties() {}
+
+    /**
+     * Sets a default value for a property. Keys use underscore notation (e.g. {@code
+     * base_time_unit}); they are translated to hyphen notation when producing {@code
+     * otel.instrumentation.*} keys.
+     *
+     * @return {@code this} for chaining
+     */
+    @CanIgnoreReturnValue
+    public InstrumentationProperties setDefault(String key, String value) {
+      properties.put(key, value);
+      return this;
+    }
+  }
 }
diff --git a/declarative-config-bridge/src/test/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaultsTest.java b/declarative-config-bridge/src/test/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaultsTest.java
index 7e89f17aafab..cc5efceef3a0 100644
--- a/declarative-config-bridge/src/test/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaultsTest.java
+++ b/declarative-config-bridge/src/test/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaultsTest.java
@@ -16,8 +16,8 @@ class InstrumentationDefaultsTest {
   @Test
   void toConfigProperties() {
     InstrumentationDefaults defaults = new InstrumentationDefaults();
-    defaults.setDefault("micrometer", "base_time_unit", "s");
-    defaults.setDefault("log4j_appender", "experimental_log_attributes", "true");
+    defaults.get("micrometer").setDefault("base_time_unit", "s");
+    defaults.get("log4j_appender").setDefault("experimental_log_attributes", "true");
 
     Map props = defaults.toConfigProperties();
 
@@ -30,7 +30,7 @@ void toConfigProperties() {
   @Test
   void applyToModel() {
     InstrumentationDefaults defaults = new InstrumentationDefaults();
-    defaults.setDefault("micrometer", "base_time_unit", "s");
+    defaults.get("micrometer").setDefault("base_time_unit", "s");
 
     OpenTelemetryConfigurationModel model = new OpenTelemetryConfigurationModel();
     defaults.applyToModel(model);
@@ -47,16 +47,15 @@ void applyToModel() {
 
   @Test
   void applyToModelDoesNotOverrideExisting() {
-    InstrumentationDefaults defaults = new InstrumentationDefaults();
-    defaults.setDefault("micrometer", "base_time_unit", "s");
-
     // Pre-populate model with a different value
     OpenTelemetryConfigurationModel model = new OpenTelemetryConfigurationModel();
-    new InstrumentationDefaults()
-        .setDefault("micrometer", "base_time_unit", "ms")
-        .applyToModel(model);
+    InstrumentationDefaults seed = new InstrumentationDefaults();
+    seed.get("micrometer").setDefault("base_time_unit", "ms");
+    seed.applyToModel(model);
 
-    // Apply defaults — should not override
+    // Apply a conflicting default — should not override
+    InstrumentationDefaults defaults = new InstrumentationDefaults();
+    defaults.get("micrometer").setDefault("base_time_unit", "s");
     defaults.applyToModel(model);
 
     assertThat(

From ea1616d625b3f0af7266a1d4d1dac21c4be3c183 Mon Sep 17 00:00:00 2001
From: Gregor Zeitlinger 
Date: Sat, 18 Apr 2026 08:33:43 +0000
Subject: [PATCH 7/7] docs(declarative-config-bridge): use get instead of
 getStructured in symmetry note

DeclarativeConfigProperties.get(name) (added in open-telemetry/opentelemetry-java#7923)
is the standard read-side API; update InstrumentationDefaults javadoc and README to
match.

Signed-off-by: Gregor Zeitlinger 
---
 declarative-config-bridge/README.md                           | 2 +-
 .../config/bridge/InstrumentationDefaults.java                | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/declarative-config-bridge/README.md b/declarative-config-bridge/README.md
index 973f89961808..ef1e6d0c452a 100644
--- a/declarative-config-bridge/README.md
+++ b/declarative-config-bridge/README.md
@@ -96,7 +96,7 @@ defaults.get("log4j_appender").setDefault("experimental_log_attributes", "true")
 ```
 
 Navigation mirrors `DeclarativeConfigProperties` — reading uses
-`config.getStructured("micrometer").getString("base_time_unit")`; writing defaults uses
+`config.get("micrometer").getString("base_time_unit")`; writing defaults uses
 `defaults.get("micrometer").setDefault("base_time_unit", "s")`.
 
 Keys use underscore notation (matching the declarative config model). They are translated to
diff --git a/declarative-config-bridge/src/main/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaults.java b/declarative-config-bridge/src/main/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaults.java
index 516d44fc602c..6d2fa7256659 100644
--- a/declarative-config-bridge/src/main/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaults.java
+++ b/declarative-config-bridge/src/main/java/io/opentelemetry/instrumentation/config/bridge/InstrumentationDefaults.java
@@ -19,7 +19,7 @@
  * declarative configuration.
  *
  * 

Navigation mirrors {@link io.opentelemetry.api.incubator.config.DeclarativeConfigProperties}: - * read-side uses {@code config.getStructured(name).getString(key)}; write-side uses {@code + * read-side uses {@code config.get(name).getString(key)}; write-side uses {@code * defaults.get(name).setDefault(key, value)}. * *

Usage: @@ -42,7 +42,7 @@ public final class InstrumentationDefaults { /** * Returns the defaults builder for the given instrumentation, creating it if absent. Mirrors - * {@code DeclarativeConfigProperties.getStructured(name)} on the read side. + * {@code DeclarativeConfigProperties.get(name)} on the read side. */ public InstrumentationProperties get(String instrumentation) { return instrumentations.computeIfAbsent(instrumentation, k -> new InstrumentationProperties());