diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ConfigPropertiesUtil.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ConfigPropertiesUtil.java
new file mode 100644
index 00000000000..7022e0cc118
--- /dev/null
+++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ConfigPropertiesUtil.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.sdk.extension.incubator.fileconfig;
+
+import io.opentelemetry.api.incubator.config.ConfigProvider;
+import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
+import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
+import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil;
+import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
+
+public class ConfigPropertiesUtil {
+ private ConfigPropertiesUtil() {}
+
+ /** Resolve {@link ConfigProperties} from the {@code autoConfiguredOpenTelemetrySdk}. */
+ public static ConfigProperties resolveConfigProperties(
+ AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) {
+ ConfigProperties sdkConfigProperties =
+ AutoConfigureUtil.getConfig(autoConfiguredOpenTelemetrySdk);
+ if (sdkConfigProperties != null) {
+ return sdkConfigProperties;
+ }
+ ConfigProvider configProvider =
+ AutoConfigureUtil.getConfigProvider(autoConfiguredOpenTelemetrySdk);
+ if (configProvider != null) {
+ DeclarativeConfigProperties instrumentationConfig = configProvider.getInstrumentationConfig();
+
+ if (instrumentationConfig == null) {
+ instrumentationConfig = DeclarativeConfigProperties.empty();
+ }
+
+ return new DeclarativeConfigPropertiesBridge(instrumentationConfig);
+ }
+ // Should never happen
+ throw new IllegalStateException(
+ "AutoConfiguredOpenTelemetrySdk does not have ConfigProperties or DeclarativeConfigProperties. This is likely a programming error in opentelemetry-java");
+ }
+}
diff --git a/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigPropertiesBridge.java b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigPropertiesBridge.java
new file mode 100644
index 00000000000..6284e877d55
--- /dev/null
+++ b/sdk-extensions/incubator/src/main/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigPropertiesBridge.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.sdk.extension.incubator.fileconfig;
+
+import static io.opentelemetry.api.incubator.config.DeclarativeConfigProperties.empty;
+
+import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
+import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
+import java.time.Duration;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import javax.annotation.Nullable;
+
+/**
+ * A {@link ConfigProperties} which resolves properties based on {@link
+ * DeclarativeConfigProperties}.
+ *
+ *
Only properties starting with "otel.instrumentation." are resolved. Others return null (or
+ * default value if provided).
+ *
+ *
To resolve:
+ *
+ *
+ * - "otel.instrumentation" refers to the ".instrumentation.java" node
+ *
- The portion of the property after "otel.instrumentation." is split into segments based on
+ * ".".
+ *
- For each N-1 segment, we walk down the tree to find the relevant leaf {@link
+ * DeclarativeConfigProperties}.
+ *
- We extract the property from the resolved {@link DeclarativeConfigProperties} using the
+ * last segment as the property key.
+ *
+ *
+ * For example, given the following YAML, asking for {@code
+ * ConfigProperties#getString("otel.instrumentation.common.string_key")} yields "value":
+ *
+ *
+ * instrumentation:
+ * java:
+ * common:
+ * string_key: value
+ *
+ */
+final class DeclarativeConfigPropertiesBridge implements ConfigProperties {
+
+ private static final String OTEL_INSTRUMENTATION_PREFIX = "otel.instrumentation.";
+
+ // The node at .instrumentation.java
+ private final DeclarativeConfigProperties instrumentationJavaNode;
+
+ DeclarativeConfigPropertiesBridge(DeclarativeConfigProperties instrumentationNode) {
+ instrumentationJavaNode = instrumentationNode.getStructured("java", empty());
+ }
+
+ @Nullable
+ @Override
+ public String getString(String propertyName) {
+ return getPropertyValue(propertyName, DeclarativeConfigProperties::getString);
+ }
+
+ @Nullable
+ @Override
+ public Boolean getBoolean(String propertyName) {
+ return getPropertyValue(propertyName, DeclarativeConfigProperties::getBoolean);
+ }
+
+ @Nullable
+ @Override
+ public Integer getInt(String propertyName) {
+ return getPropertyValue(propertyName, DeclarativeConfigProperties::getInt);
+ }
+
+ @Nullable
+ @Override
+ public Long getLong(String propertyName) {
+ return getPropertyValue(propertyName, DeclarativeConfigProperties::getLong);
+ }
+
+ @Nullable
+ @Override
+ public Double getDouble(String propertyName) {
+ return getPropertyValue(propertyName, DeclarativeConfigProperties::getDouble);
+ }
+
+ @Nullable
+ @Override
+ public Duration getDuration(String propertyName) {
+ Long millis = getPropertyValue(propertyName, DeclarativeConfigProperties::getLong);
+ if (millis == null) {
+ return null;
+ }
+ return Duration.ofMillis(millis);
+ }
+
+ @Override
+ public List getList(String propertyName) {
+ List propertyValue =
+ getPropertyValue(
+ propertyName,
+ (properties, lastPart) -> properties.getScalarList(lastPart, String.class));
+ return propertyValue == null ? Collections.emptyList() : propertyValue;
+ }
+
+ @Override
+ public Map getMap(String propertyName) {
+ DeclarativeConfigProperties propertyValue =
+ getPropertyValue(propertyName, DeclarativeConfigProperties::getStructured);
+ if (propertyValue == null) {
+ return Collections.emptyMap();
+ }
+ Map result = new HashMap<>();
+ propertyValue
+ .getPropertyKeys()
+ .forEach(
+ key -> {
+ String value = propertyValue.getString(key);
+ if (value == null) {
+ return;
+ }
+ result.put(key, value);
+ });
+ return Collections.unmodifiableMap(result);
+ }
+
+ @Nullable
+ private T getPropertyValue(
+ String property, BiFunction extractor) {
+ if (instrumentationJavaNode == null) {
+ return null;
+ }
+
+ if (property.startsWith(OTEL_INSTRUMENTATION_PREFIX)) {
+ property = property.substring(OTEL_INSTRUMENTATION_PREFIX.length());
+ }
+ // Split the remainder of the property on "."
+ String[] segments = property.split("\\.");
+ if (segments.length == 0) {
+ return null;
+ }
+
+ // Extract the value by walking to the N-1 entry
+ DeclarativeConfigProperties target = instrumentationJavaNode;
+ if (segments.length > 1) {
+ for (int i = 0; i < segments.length - 1; i++) {
+ target = target.getStructured(segments[i], empty());
+ }
+ }
+ String lastPart = segments[segments.length - 1];
+
+ return extractor.apply(target, lastPart);
+ }
+}
diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ConfigPropertiesUtilTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ConfigPropertiesUtilTest.java
new file mode 100644
index 00000000000..14e4ce7a5a3
--- /dev/null
+++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/ConfigPropertiesUtilTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.sdk.extension.incubator.fileconfig;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import io.opentelemetry.api.incubator.config.ConfigProvider;
+import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
+import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
+import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil;
+import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+
+@SuppressWarnings("DoNotMockAutoValue")
+class ConfigPropertiesUtilTest {
+ @Test
+ void shouldUseConfigPropertiesForAutoConfiguration() {
+ ConfigProperties configPropertiesMock = mock(ConfigProperties.class);
+ AutoConfiguredOpenTelemetrySdk sdkMock = mock(AutoConfiguredOpenTelemetrySdk.class);
+ try (MockedStatic autoConfigureUtilMock =
+ Mockito.mockStatic(AutoConfigureUtil.class)) {
+ autoConfigureUtilMock
+ .when(() -> AutoConfigureUtil.getConfig(sdkMock))
+ .thenReturn(configPropertiesMock);
+
+ ConfigProperties configProperties = ConfigPropertiesUtil.resolveConfigProperties(sdkMock);
+
+ assertThat(configProperties).isSameAs(configPropertiesMock);
+ }
+ }
+
+ @Test
+ void shouldUseConfigProviderForDeclarativeConfiguration() {
+ String propertyName = "testProperty";
+ String expectedValue = "the value";
+ DeclarativeConfigProperties javaNodeMock = mock(DeclarativeConfigProperties.class);
+ when(javaNodeMock.getString(propertyName)).thenReturn(expectedValue);
+
+ DeclarativeConfigProperties instrumentationConfigMock = mock(DeclarativeConfigProperties.class);
+ when(instrumentationConfigMock.getStructured(eq("java"), any())).thenReturn(javaNodeMock);
+
+ ConfigProvider configProviderMock = mock(ConfigProvider.class);
+ when(configProviderMock.getInstrumentationConfig()).thenReturn(instrumentationConfigMock);
+
+ AutoConfiguredOpenTelemetrySdk sdkMock = mock(AutoConfiguredOpenTelemetrySdk.class);
+
+ try (MockedStatic autoConfigureUtilMock =
+ Mockito.mockStatic(AutoConfigureUtil.class)) {
+ autoConfigureUtilMock.when(() -> AutoConfigureUtil.getConfig(sdkMock)).thenReturn(null);
+ autoConfigureUtilMock
+ .when(() -> AutoConfigureUtil.getConfigProvider(sdkMock))
+ .thenReturn(configProviderMock);
+
+ ConfigProperties configProperties = ConfigPropertiesUtil.resolveConfigProperties(sdkMock);
+
+ assertThat(configProperties.getString(propertyName)).isEqualTo(expectedValue);
+ }
+ }
+
+ @Test
+ void shouldUseConfigProviderForDeclarativeConfiguration_noInstrumentationConfig() {
+ AutoConfiguredOpenTelemetrySdk sdkMock = mock(AutoConfiguredOpenTelemetrySdk.class);
+ ConfigProvider configProviderMock = mock(ConfigProvider.class);
+ when(configProviderMock.getInstrumentationConfig()).thenReturn(null);
+
+ try (MockedStatic autoConfigureUtilMock =
+ Mockito.mockStatic(AutoConfigureUtil.class)) {
+ autoConfigureUtilMock.when(() -> AutoConfigureUtil.getConfig(sdkMock)).thenReturn(null);
+ autoConfigureUtilMock
+ .when(() -> AutoConfigureUtil.getConfigProvider(sdkMock))
+ .thenReturn(configProviderMock);
+
+ ConfigProperties configProperties = ConfigPropertiesUtil.resolveConfigProperties(sdkMock);
+
+ assertThat(configProperties.getString("testProperty")).isEqualTo(null);
+ }
+ }
+}
diff --git a/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigPropertiesBridgeTest.java b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigPropertiesBridgeTest.java
new file mode 100644
index 00000000000..d72840e474c
--- /dev/null
+++ b/sdk-extensions/incubator/src/test/java/io/opentelemetry/sdk/extension/incubator/fileconfig/DeclarativeConfigPropertiesBridgeTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.sdk.extension.incubator.fileconfig;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
+import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.InstrumentationModel;
+import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfigurationModel;
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class DeclarativeConfigPropertiesBridgeTest {
+
+ private static final String YAML =
+ "file_format: 0.4\n"
+ + "instrumentation/development:\n"
+ + " java:\n"
+ + " common:\n"
+ + " default-enabled: true\n"
+ + " runtime-telemetry:\n"
+ + " enabled: false\n"
+ + " example-instrumentation:\n"
+ + " string_key: value\n"
+ + " bool_key: true\n"
+ + " int_key: 1\n"
+ + " double_key: 1.1\n"
+ + " list_key:\n"
+ + " - value1\n"
+ + " - value2\n"
+ + " - true\n"
+ + " map_key:\n"
+ + " string_key1: value1\n"
+ + " string_key2: value2\n"
+ + " bool_key: true\n"
+ + " acme:\n"
+ + " full_name:\n"
+ + " preserved: true";
+
+ private ConfigProperties bridge;
+ private ConfigProperties emptyBridge;
+
+ @BeforeEach
+ void setup() {
+ OpenTelemetryConfigurationModel model =
+ DeclarativeConfiguration.parse(
+ new ByteArrayInputStream(YAML.getBytes(StandardCharsets.UTF_8)));
+ SdkConfigProvider configProvider = SdkConfigProvider.create(model);
+ bridge =
+ new DeclarativeConfigPropertiesBridge(
+ Objects.requireNonNull(configProvider.getInstrumentationConfig()));
+
+ OpenTelemetryConfigurationModel emptyModel =
+ new OpenTelemetryConfigurationModel()
+ .withAdditionalProperty("instrumentation/development", new InstrumentationModel());
+ SdkConfigProvider emptyConfigProvider = SdkConfigProvider.create(emptyModel);
+ emptyBridge =
+ new DeclarativeConfigPropertiesBridge(
+ Objects.requireNonNull(emptyConfigProvider.getInstrumentationConfig()));
+ }
+
+ @Test
+ void getProperties() {
+ // only properties starting with "otel.instrumentation." are resolved
+ // asking for properties which don't exist or inaccessible shouldn't result in an error
+ assertThat(bridge.getString("file_format")).isNull();
+ assertThat(bridge.getString("file_format", "foo")).isEqualTo("foo");
+ assertThat(emptyBridge.getBoolean("otel.instrumentation.common.default-enabled")).isNull();
+ assertThat(emptyBridge.getBoolean("otel.instrumentation.common.default-enabled", true))
+ .isTrue();
+
+ // common cases
+ assertThat(bridge.getBoolean("otel.instrumentation.common.default-enabled")).isTrue();
+ assertThat(bridge.getBoolean("otel.instrumentation.runtime-telemetry.enabled")).isFalse();
+
+ // check all the types
+ Map expectedMap = new HashMap<>();
+ expectedMap.put("string_key1", "value1");
+ expectedMap.put("string_key2", "value2");
+ assertThat(bridge.getString("otel.instrumentation.example-instrumentation.string_key"))
+ .isEqualTo("value");
+ assertThat(bridge.getBoolean("otel.instrumentation.example-instrumentation.bool_key")).isTrue();
+ assertThat(bridge.getInt("otel.instrumentation.example-instrumentation.int_key")).isEqualTo(1);
+ assertThat(bridge.getLong("otel.instrumentation.example-instrumentation.int_key"))
+ .isEqualTo(1L);
+ assertThat(bridge.getDuration("otel.instrumentation.example-instrumentation.int_key"))
+ .isEqualTo(Duration.ofMillis(1));
+ assertThat(bridge.getDouble("otel.instrumentation.example-instrumentation.double_key"))
+ .isEqualTo(1.1);
+ assertThat(bridge.getList("otel.instrumentation.example-instrumentation.list_key"))
+ .isEqualTo(Arrays.asList("value1", "value2"));
+ assertThat(bridge.getMap("otel.instrumentation.example-instrumentation.map_key"))
+ .isEqualTo(expectedMap);
+
+ // asking for properties with the wrong type returns null
+ assertThat(bridge.getBoolean("otel.instrumentation.example-instrumentation.string_key"))
+ .isNull();
+ assertThat(bridge.getString("otel.instrumentation.example-instrumentation.bool_key")).isNull();
+ assertThat(bridge.getString("otel.instrumentation.example-instrumentation.int_key")).isNull();
+ assertThat(bridge.getString("otel.instrumentation.example-instrumentation.double_key"))
+ .isNull();
+ assertThat(bridge.getString("otel.instrumentation.example-instrumentation.list_key")).isNull();
+ assertThat(bridge.getString("otel.instrumentation.example-instrumentation.map_key")).isNull();
+
+ // check all the types
+ assertThat(bridge.getString("otel.instrumentation.other-instrumentation.string_key", "value"))
+ .isEqualTo("value");
+ assertThat(bridge.getBoolean("otel.instrumentation.other-instrumentation.bool_key", true))
+ .isTrue();
+ assertThat(bridge.getInt("otel.instrumentation.other-instrumentation.int_key", 1)).isEqualTo(1);
+ assertThat(bridge.getLong("otel.instrumentation.other-instrumentation.int_key", 1L))
+ .isEqualTo(1L);
+ assertThat(
+ bridge.getDuration(
+ "otel.instrumentation.other-instrumentation.int_key", Duration.ofMillis(1)))
+ .isEqualTo(Duration.ofMillis(1));
+ assertThat(bridge.getDouble("otel.instrumentation.other-instrumentation.double_key", 1.1))
+ .isEqualTo(1.1);
+ assertThat(
+ bridge.getList(
+ "otel.instrumentation.other-instrumentation.list_key",
+ Arrays.asList("value1", "value2")))
+ .isEqualTo(Arrays.asList("value1", "value2"));
+ assertThat(bridge.getMap("otel.instrumentation.other-instrumentation.map_key", expectedMap))
+ .isEqualTo(expectedMap);
+
+ // verify vendor specific property names are preserved in unchanged form (prefix is not stripped
+ // as for otel.instrumentation.*)
+ assertThat(bridge.getBoolean("acme.full_name.preserved")).isTrue();
+ }
+}