From 9d975b9586d58d3579bdd42ffb8f7ca268a8a83d Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Fri, 24 Oct 2025 08:17:41 -0400 Subject: [PATCH 1/6] add bean override and cache for spring environment calls --- .../OpenTelemetryAutoConfiguration.java | 1 + .../properties/SpringConfigProperties.java | 201 ++++++++++++------ .../OpenTelemetryAutoConfigurationTest.java | 22 ++ .../SpringConfigPropertiesTest.java | 70 +++++- 4 files changed, 232 insertions(+), 62 deletions(-) diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java index b9a9bd7c9a2a..1d894e0330dc 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java @@ -100,6 +100,7 @@ public ResourceProvider otelDistroVersionResourceProvider() { } @Bean + @ConditionalOnMissingBean public AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk( Environment env, OtlpExporterProperties otlpExporterProperties, diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java index 4f0412ef023b..3111568fe34d 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java @@ -15,6 +15,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; import org.springframework.core.env.Environment; import org.springframework.expression.ExpressionParser; @@ -34,6 +36,23 @@ public class SpringConfigProperties implements ConfigProperties { private final ConfigProperties customizedListProperties; private final Map> listPropertyValues; + private final ConcurrentHashMap> cachedStringValues = + new ConcurrentHashMap<>(); + private final ConcurrentHashMap> cachedBooleanValues = + new ConcurrentHashMap<>(); + private final ConcurrentHashMap> cachedIntValues = + new ConcurrentHashMap<>(); + private final ConcurrentHashMap> cachedLongValues = + new ConcurrentHashMap<>(); + private final ConcurrentHashMap> cachedDoubleValues = + new ConcurrentHashMap<>(); + private final ConcurrentHashMap>> cachedListValues = + new ConcurrentHashMap<>(); + private final ConcurrentHashMap> cachedDurationValues = + new ConcurrentHashMap<>(); + private final ConcurrentHashMap>> cachedMapValues = + new ConcurrentHashMap<>(); + static final String DISABLED_KEY = "otel.java.disabled.resource.providers"; static final String ENABLED_KEY = "otel.java.enabled.resource.providers"; @@ -151,106 +170,166 @@ public static ConfigProperties create( @Nullable @Override public String getString(String name) { - String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(name); - String value = environment.getProperty(normalizedName, String.class); - if (value == null && normalizedName.equals("otel.exporter.otlp.protocol")) { - // SDK autoconfigure module defaults to `grpc`, but this module aligns with recommendation - // in specification to default to `http/protobuf` - return OtlpConfigUtil.PROTOCOL_HTTP_PROTOBUF; - } - return or(value, otelSdkProperties.getString(name)); + return cachedStringValues + .computeIfAbsent( + name, + key -> { + String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(key); + String value = environment.getProperty(normalizedName, String.class); + if (value == null && normalizedName.equals("otel.exporter.otlp.protocol")) { + // SDK autoconfigure module defaults to `grpc`, but this module aligns with + // recommendation in specification to default to `http/protobuf` + return Optional.of(OtlpConfigUtil.PROTOCOL_HTTP_PROTOBUF); + } + return Optional.ofNullable(or(value, otelSdkProperties.getString(key))); + }) + .orElse(null); } @Nullable @Override public Boolean getBoolean(String name) { - return or( - environment.getProperty(ConfigUtil.normalizeEnvironmentVariableKey(name), Boolean.class), - otelSdkProperties.getBoolean(name)); + return cachedBooleanValues + .computeIfAbsent( + name, + key -> + Optional.ofNullable( + or( + environment.getProperty( + ConfigUtil.normalizeEnvironmentVariableKey(name), Boolean.class), + otelSdkProperties.getBoolean(name)))) + .orElse(null); } @Nullable @Override public Integer getInt(String name) { - return or( - environment.getProperty(ConfigUtil.normalizeEnvironmentVariableKey(name), Integer.class), - otelSdkProperties.getInt(name)); + return cachedIntValues + .computeIfAbsent( + name, + key -> + Optional.ofNullable( + or( + environment.getProperty( + ConfigUtil.normalizeEnvironmentVariableKey(key), Integer.class), + otelSdkProperties.getInt(key)))) + .orElse(null); } @Nullable @Override public Long getLong(String name) { - return or( - environment.getProperty(ConfigUtil.normalizeEnvironmentVariableKey(name), Long.class), - otelSdkProperties.getLong(name)); + return cachedLongValues + .computeIfAbsent( + name, + key -> + Optional.ofNullable( + or( + environment.getProperty( + ConfigUtil.normalizeEnvironmentVariableKey(key), Long.class), + otelSdkProperties.getLong(key)))) + .orElse(null); } @Nullable @Override public Double getDouble(String name) { - return or( - environment.getProperty(ConfigUtil.normalizeEnvironmentVariableKey(name), Double.class), - otelSdkProperties.getDouble(name)); + return cachedDoubleValues + .computeIfAbsent( + name, + key -> + Optional.ofNullable( + or( + environment.getProperty( + ConfigUtil.normalizeEnvironmentVariableKey(key), Double.class), + otelSdkProperties.getDouble(key)))) + .orElse(null); } @SuppressWarnings("unchecked") @Override public List getList(String name) { - String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(name); + return cachedListValues + .computeIfAbsent( + name, + key -> { + String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(key); - List list = listPropertyValues.get(normalizedName); - if (list != null) { - List c = customizedListProperties.getList(name); - if (!c.isEmpty()) { - return c; - } - if (!list.isEmpty()) { - return list; - } - } + List list = listPropertyValues.get(normalizedName); + if (list != null) { + List c = customizedListProperties.getList(key); + if (!c.isEmpty()) { + return Optional.of(c); + } + if (!list.isEmpty()) { + return Optional.of(list); + } + } - return or(environment.getProperty(normalizedName, List.class), otelSdkProperties.getList(name)); + List envValue = + (List) environment.getProperty(normalizedName, List.class); + return Optional.ofNullable(or(envValue, otelSdkProperties.getList(key))); + }) + .orElse(null); } @Nullable @Override public Duration getDuration(String name) { - String value = getString(name); - if (value == null) { - return otelSdkProperties.getDuration(name); - } - return DefaultConfigProperties.createFromMap(Collections.singletonMap(name, value)) - .getDuration(name); + return cachedDurationValues + .computeIfAbsent( + name, + key -> { + String value = getString(key); + if (value == null) { + return Optional.ofNullable(otelSdkProperties.getDuration(key)); + } + return Optional.ofNullable( + DefaultConfigProperties.createFromMap(Collections.singletonMap(key, value)) + .getDuration(key)); + }) + .orElse(null); } @SuppressWarnings("unchecked") @Override public Map getMap(String name) { - Map otelSdkMap = otelSdkProperties.getMap(name); - - String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(name); - // maps from config properties are not supported by Environment, so we have to fake it - switch (normalizedName) { - case "otel.resource.attributes": - return mergeWithOtel(resourceProperties.getAttributes(), otelSdkMap); - case "otel.exporter.otlp.headers": - return mergeWithOtel(otlpExporterProperties.getHeaders(), otelSdkMap); - case "otel.exporter.otlp.logs.headers": - return mergeWithOtel(otlpExporterProperties.getLogs().getHeaders(), otelSdkMap); - case "otel.exporter.otlp.metrics.headers": - return mergeWithOtel(otlpExporterProperties.getMetrics().getHeaders(), otelSdkMap); - case "otel.exporter.otlp.traces.headers": - return mergeWithOtel(otlpExporterProperties.getTraces().getHeaders(), otelSdkMap); - default: - break; - } + return cachedMapValues + .computeIfAbsent( + name, + key -> { + Map otelSdkMap = otelSdkProperties.getMap(name); - String value = environment.getProperty(normalizedName); - if (value == null) { - return otelSdkMap; - } - return (Map) parser.parseExpression(value).getValue(); + String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(name); + // maps from config properties are not supported by Environment, so we have to fake it + switch (normalizedName) { + case "otel.resource.attributes": + return Optional.of(mergeWithOtel(resourceProperties.getAttributes(), otelSdkMap)); + case "otel.exporter.otlp.headers": + return Optional.of( + mergeWithOtel(otlpExporterProperties.getHeaders(), otelSdkMap)); + case "otel.exporter.otlp.logs.headers": + return Optional.of( + mergeWithOtel(otlpExporterProperties.getLogs().getHeaders(), otelSdkMap)); + case "otel.exporter.otlp.metrics.headers": + return Optional.of( + mergeWithOtel(otlpExporterProperties.getMetrics().getHeaders(), otelSdkMap)); + case "otel.exporter.otlp.traces.headers": + return Optional.of( + mergeWithOtel(otlpExporterProperties.getTraces().getHeaders(), otelSdkMap)); + default: + break; + } + + String value = environment.getProperty(normalizedName); + if (value == null) { + return Optional.of(otelSdkMap); + } + return Optional.ofNullable( + (Map) parser.parseExpression(value).getValue()); + }) + .orElse(null); } /** diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java index 1e1a04bb5f10..af5ce6e12dc8 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java @@ -12,6 +12,7 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.exporter.otlp.internal.OtlpSpanExporterProvider; import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.internal.AutoConfigureListener; import io.opentelemetry.sdk.trace.export.SpanExporter; @@ -58,6 +59,27 @@ void customOpenTelemetry() { .hasBean("otelProperties")); } + private static class CustomAutoConfiguredSdkConfiguration { + @Bean + public AutoConfiguredOpenTelemetrySdk customAutoConfiguredSdk() { + return AutoConfiguredOpenTelemetrySdk.builder().build(); + } + } + + @Test + @DisplayName( + "when Application Context contains AutoConfiguredOpenTelemetrySdk bean should NOT use default") + void customAutoConfiguredOpenTelemetrySdk() { + this.contextRunner + .withUserConfiguration(CustomAutoConfiguredSdkConfiguration.class) + .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)) + .run( + context -> + assertThat(context) + .hasBean("customAutoConfiguredSdk") + .doesNotHaveBean("autoConfiguredOpenTelemetrySdk")); + } + @Test @DisplayName( "when Application Context DOES NOT contain OpenTelemetry bean should initialize openTelemetry") diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigPropertiesTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigPropertiesTest.java index 02433342b1ab..61ea7ed3905a 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigPropertiesTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigPropertiesTest.java @@ -5,8 +5,13 @@ package io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties; +import static java.util.Collections.emptyMap; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.spring.autoconfigure.OpenTelemetryAutoConfiguration; @@ -17,6 +22,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import java.util.stream.Stream; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -162,8 +168,70 @@ void shouldInitializeAttributesByMap() { }); } + public static Stream propertyCachingTestCases() { + return Stream.of( + // property, typeClass, assertion + Arguments.of( + "otel.service.name=test-service", + String.class, + (Consumer) + config -> + assertThat(config.getString("otel.service.name")).isEqualTo("test-service")), + Arguments.of( + "otel.exporter.otlp.enabled=true", + Boolean.class, + (Consumer) + config -> assertThat(config.getBoolean("otel.exporter.otlp.enabled")).isTrue()), + Arguments.of( + "otel.metric.export.interval=10", + Integer.class, + (Consumer) + config -> assertThat(config.getInt("otel.metric.export.interval")).isEqualTo(10)), + Arguments.of( + "otel.bsp.schedule.delay=5000", + Long.class, + (Consumer) + config -> assertThat(config.getLong("otel.bsp.schedule.delay")).isEqualTo(5000L)), + Arguments.of( + "otel.traces.sampler.arg=0.5", + Double.class, + (Consumer) + config -> assertThat(config.getDouble("otel.traces.sampler.arg")).isEqualTo(0.5))); + } + + @ParameterizedTest + @MethodSource("propertyCachingTestCases") + @DisplayName("should cache property lookups and call Environment.getProperty() only once") + void propertyCaching( + String property, Class typeClass, Consumer assertion) { + String propertyName = property.split("=")[0]; + + this.contextRunner + .withPropertyValues(property) + .run( + context -> { + Environment realEnvironment = context.getBean("environment", Environment.class); + Environment spyEnvironment = spy(realEnvironment); + + SpringConfigProperties config = + new SpringConfigProperties( + spyEnvironment, + new SpelExpressionParser(), + context.getBean(OtlpExporterProperties.class), + context.getBean(OtelResourceProperties.class), + context.getBean(OtelSpringProperties.class), + DefaultConfigProperties.createFromMap(emptyMap())); + + for (int i = 0; i < 100; i++) { + assertion.accept(config); + } + + verify(spyEnvironment, times(1)).getProperty(eq(propertyName), eq(typeClass)); + }); + } + private static ConfigProperties getConfig(AssertableApplicationContext context) { - return getConfig(context, Collections.emptyMap()); + return getConfig(context, emptyMap()); } private static SpringConfigProperties getConfig( From 39757e14246bbd005ac4cd9361a78dd28ae791cd Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Fri, 24 Oct 2025 09:57:34 -0400 Subject: [PATCH 2/6] use helper methods --- .../properties/SpringConfigProperties.java | 251 +++++++++--------- 1 file changed, 126 insertions(+), 125 deletions(-) diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java index 3111568fe34d..71400bce5daa 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java @@ -17,6 +17,7 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; import javax.annotation.Nullable; import org.springframework.core.env.Environment; import org.springframework.expression.ExpressionParser; @@ -167,169 +168,169 @@ public static ConfigProperties create( fallback); } + @Nullable + private static T getCachedValue( + ConcurrentHashMap> cache, + String name, + Function valueFunction) { + return cache + .computeIfAbsent(name, key -> Optional.ofNullable(valueFunction.apply(key))) + .orElse(null); + } + @Nullable @Override public String getString(String name) { - return cachedStringValues - .computeIfAbsent( - name, - key -> { - String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(key); - String value = environment.getProperty(normalizedName, String.class); - if (value == null && normalizedName.equals("otel.exporter.otlp.protocol")) { - // SDK autoconfigure module defaults to `grpc`, but this module aligns with - // recommendation in specification to default to `http/protobuf` - return Optional.of(OtlpConfigUtil.PROTOCOL_HTTP_PROTOBUF); - } - return Optional.ofNullable(or(value, otelSdkProperties.getString(key))); - }) - .orElse(null); + return getCachedValue( + cachedStringValues, + name, + key -> { + String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(key); + String value = environment.getProperty(normalizedName, String.class); + if (value == null && normalizedName.equals("otel.exporter.otlp.protocol")) { + // SDK autoconfigure module defaults to `grpc`, but this module aligns with + // recommendation in specification to default to `http/protobuf` + return OtlpConfigUtil.PROTOCOL_HTTP_PROTOBUF; + } + return or(value, otelSdkProperties.getString(key)); + }); } @Nullable @Override public Boolean getBoolean(String name) { - return cachedBooleanValues - .computeIfAbsent( - name, - key -> - Optional.ofNullable( - or( - environment.getProperty( - ConfigUtil.normalizeEnvironmentVariableKey(name), Boolean.class), - otelSdkProperties.getBoolean(name)))) - .orElse(null); + return getCachedValue( + cachedBooleanValues, + name, + key -> + or( + environment.getProperty( + ConfigUtil.normalizeEnvironmentVariableKey(key), Boolean.class), + otelSdkProperties.getBoolean(key))); } @Nullable @Override public Integer getInt(String name) { - return cachedIntValues - .computeIfAbsent( - name, - key -> - Optional.ofNullable( - or( - environment.getProperty( - ConfigUtil.normalizeEnvironmentVariableKey(key), Integer.class), - otelSdkProperties.getInt(key)))) - .orElse(null); + return getCachedValue( + cachedIntValues, + name, + key -> + or( + environment.getProperty( + ConfigUtil.normalizeEnvironmentVariableKey(key), Integer.class), + otelSdkProperties.getInt(key))); } @Nullable @Override public Long getLong(String name) { - return cachedLongValues - .computeIfAbsent( - name, - key -> - Optional.ofNullable( - or( - environment.getProperty( - ConfigUtil.normalizeEnvironmentVariableKey(key), Long.class), - otelSdkProperties.getLong(key)))) - .orElse(null); + return getCachedValue( + cachedLongValues, + name, + key -> + or( + environment.getProperty( + ConfigUtil.normalizeEnvironmentVariableKey(key), Long.class), + otelSdkProperties.getLong(key))); } @Nullable @Override public Double getDouble(String name) { - return cachedDoubleValues - .computeIfAbsent( - name, - key -> - Optional.ofNullable( - or( - environment.getProperty( - ConfigUtil.normalizeEnvironmentVariableKey(key), Double.class), - otelSdkProperties.getDouble(key)))) - .orElse(null); + return getCachedValue( + cachedDoubleValues, + name, + key -> + or( + environment.getProperty( + ConfigUtil.normalizeEnvironmentVariableKey(key), Double.class), + otelSdkProperties.getDouble(key))); } @SuppressWarnings("unchecked") @Override public List getList(String name) { - - return cachedListValues - .computeIfAbsent( - name, - key -> { - String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(key); - - List list = listPropertyValues.get(normalizedName); - if (list != null) { - List c = customizedListProperties.getList(key); - if (!c.isEmpty()) { - return Optional.of(c); - } - if (!list.isEmpty()) { - return Optional.of(list); - } - } - - List envValue = - (List) environment.getProperty(normalizedName, List.class); - return Optional.ofNullable(or(envValue, otelSdkProperties.getList(key))); - }) - .orElse(null); + return getCachedValue( + cachedListValues, + name, + key -> { + String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(key); + + List list = listPropertyValues.get(normalizedName); + if (list != null) { + List c = customizedListProperties.getList(key); + if (!c.isEmpty()) { + return c; + } + if (!list.isEmpty()) { + return list; + } + } + + List envValue = + (List) environment.getProperty(normalizedName, List.class); + return or(envValue, otelSdkProperties.getList(key)); + }); } @Nullable @Override public Duration getDuration(String name) { - return cachedDurationValues - .computeIfAbsent( - name, - key -> { - String value = getString(key); - if (value == null) { - return Optional.ofNullable(otelSdkProperties.getDuration(key)); - } - return Optional.ofNullable( - DefaultConfigProperties.createFromMap(Collections.singletonMap(key, value)) - .getDuration(key)); - }) - .orElse(null); + return getCachedValue( + cachedDurationValues, + name, + key -> { + String value = getString(key); + if (value == null) { + return otelSdkProperties.getDuration(key); + } + return DefaultConfigProperties.createFromMap(Collections.singletonMap(key, value)) + .getDuration(key); + }); } @SuppressWarnings("unchecked") @Override public Map getMap(String name) { - return cachedMapValues - .computeIfAbsent( - name, - key -> { - Map otelSdkMap = otelSdkProperties.getMap(name); - - String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(name); - // maps from config properties are not supported by Environment, so we have to fake it - switch (normalizedName) { - case "otel.resource.attributes": - return Optional.of(mergeWithOtel(resourceProperties.getAttributes(), otelSdkMap)); - case "otel.exporter.otlp.headers": - return Optional.of( - mergeWithOtel(otlpExporterProperties.getHeaders(), otelSdkMap)); - case "otel.exporter.otlp.logs.headers": - return Optional.of( - mergeWithOtel(otlpExporterProperties.getLogs().getHeaders(), otelSdkMap)); - case "otel.exporter.otlp.metrics.headers": - return Optional.of( - mergeWithOtel(otlpExporterProperties.getMetrics().getHeaders(), otelSdkMap)); - case "otel.exporter.otlp.traces.headers": - return Optional.of( - mergeWithOtel(otlpExporterProperties.getTraces().getHeaders(), otelSdkMap)); - default: - break; - } - - String value = environment.getProperty(normalizedName); - if (value == null) { - return Optional.of(otelSdkMap); - } - return Optional.ofNullable( - (Map) parser.parseExpression(value).getValue()); - }) - .orElse(null); + return getCachedValue( + cachedMapValues, + name, + key -> { + Map otelSdkMap = otelSdkProperties.getMap(key); + + String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(key); + // maps from config properties are not supported by Environment, so we have to fake it + Map specialMap = getSpecialMapProperty(normalizedName, otelSdkMap); + if (specialMap != null) { + return specialMap; + } + + String value = environment.getProperty(normalizedName); + if (value == null) { + return otelSdkMap; + } + return (Map) parser.parseExpression(value).getValue(); + }); + } + + @Nullable + private Map getSpecialMapProperty( + String normalizedName, Map otelSdkMap) { + switch (normalizedName) { + case "otel.resource.attributes": + return mergeWithOtel(resourceProperties.getAttributes(), otelSdkMap); + case "otel.exporter.otlp.headers": + return mergeWithOtel(otlpExporterProperties.getHeaders(), otelSdkMap); + case "otel.exporter.otlp.logs.headers": + return mergeWithOtel(otlpExporterProperties.getLogs().getHeaders(), otelSdkMap); + case "otel.exporter.otlp.metrics.headers": + return mergeWithOtel(otlpExporterProperties.getMetrics().getHeaders(), otelSdkMap); + case "otel.exporter.otlp.traces.headers": + return mergeWithOtel(otlpExporterProperties.getTraces().getHeaders(), otelSdkMap); + default: + return null; + } } /** From 5fa3e39e3c03ad7e9ceed6bff3c5351a07eab6b3 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Mon, 3 Nov 2025 13:32:38 -0500 Subject: [PATCH 3/6] implement cached property resolver --- .../properties/CachedPropertyResolver.java | 97 ++++++++++ .../properties/SpringConfigProperties.java | 181 ++++++------------ .../SpringConfigPropertiesTest.java | 52 ++++- 3 files changed, 205 insertions(+), 125 deletions(-) create mode 100644 instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/CachedPropertyResolver.java diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/CachedPropertyResolver.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/CachedPropertyResolver.java new file mode 100644 index 000000000000..8fa3d948303d --- /dev/null +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/CachedPropertyResolver.java @@ -0,0 +1,97 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties; + +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nullable; +import org.springframework.core.env.Environment; + +/** + * A caching wrapper around Spring's {@link Environment} that caches property lookups to avoid + * repeated expensive operations and property source traversal. + * + *

Thread-safe for concurrent access. Cached values persist indefinitely and are assumed to be + * immutable after the first access. If properties can change at runtime, use {@link #clear()} to + * invalidate the cache. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +final class CachedPropertyResolver { + private final Environment environment; + private final ConcurrentHashMap> cache = new ConcurrentHashMap<>(); + + CachedPropertyResolver(Environment environment) { + this.environment = Objects.requireNonNull(environment, "environment"); + } + + /** + * Gets a property value from the environment, using a cache to avoid repeated lookups. + * + * @param name the property name + * @param targetType the target type to convert to + * @return the property value, or null if not found + */ + @Nullable + T getProperty(String name, Class targetType) { + CacheKey key = new CacheKey(name, targetType); + // CacheKey includes targetType, ensuring type match + @SuppressWarnings("unchecked") + Optional result = + (Optional) + cache.computeIfAbsent( + key, k -> Optional.ofNullable(environment.getProperty(name, targetType))); + return result.orElse(null); + } + + /** + * Gets a string property value from the environment. + * + * @param name the property name + * @return the property value, or null if not found + */ + @Nullable + String getProperty(String name) { + return getProperty(name, String.class); + } + + /** Clears all cached property values, forcing fresh lookups on subsequent calls. */ + void clear() { + cache.clear(); + } + + /** Cache key combining property name and target type. */ + private static final class CacheKey { + private final String name; + private final Class targetType; + private final int cachedHashCode; + + CacheKey(String name, Class targetType) { + this.name = Objects.requireNonNull(name, "name"); + this.targetType = Objects.requireNonNull(targetType, "targetType"); + this.cachedHashCode = Objects.hash(name, targetType); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof CacheKey)) { + return false; + } + CacheKey other = (CacheKey) obj; + return name.equals(other.name) && targetType.equals(other.targetType); + } + + @Override + public int hashCode() { + return cachedHashCode; + } + } +} diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java index 71400bce5daa..ea760ca05198 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java @@ -15,9 +15,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; import javax.annotation.Nullable; import org.springframework.core.env.Environment; import org.springframework.expression.ExpressionParser; @@ -28,7 +25,7 @@ * any time. */ public class SpringConfigProperties implements ConfigProperties { - private final Environment environment; + private final CachedPropertyResolver cachedEnvironment; private final ExpressionParser parser; private final OtlpExporterProperties otlpExporterProperties; @@ -37,23 +34,6 @@ public class SpringConfigProperties implements ConfigProperties { private final ConfigProperties customizedListProperties; private final Map> listPropertyValues; - private final ConcurrentHashMap> cachedStringValues = - new ConcurrentHashMap<>(); - private final ConcurrentHashMap> cachedBooleanValues = - new ConcurrentHashMap<>(); - private final ConcurrentHashMap> cachedIntValues = - new ConcurrentHashMap<>(); - private final ConcurrentHashMap> cachedLongValues = - new ConcurrentHashMap<>(); - private final ConcurrentHashMap> cachedDoubleValues = - new ConcurrentHashMap<>(); - private final ConcurrentHashMap>> cachedListValues = - new ConcurrentHashMap<>(); - private final ConcurrentHashMap> cachedDurationValues = - new ConcurrentHashMap<>(); - private final ConcurrentHashMap>> cachedMapValues = - new ConcurrentHashMap<>(); - static final String DISABLED_KEY = "otel.java.disabled.resource.providers"; static final String ENABLED_KEY = "otel.java.enabled.resource.providers"; @@ -64,7 +44,7 @@ public SpringConfigProperties( OtelResourceProperties resourceProperties, OtelSpringProperties otelSpringProperties, ConfigProperties otelSdkProperties) { - this.environment = environment; + this.cachedEnvironment = new CachedPropertyResolver(environment); this.parser = parser; this.otlpExporterProperties = otlpExporterProperties; this.resourceProperties = resourceProperties; @@ -168,150 +148,103 @@ public static ConfigProperties create( fallback); } - @Nullable - private static T getCachedValue( - ConcurrentHashMap> cache, - String name, - Function valueFunction) { - return cache - .computeIfAbsent(name, key -> Optional.ofNullable(valueFunction.apply(key))) - .orElse(null); - } - @Nullable @Override public String getString(String name) { - return getCachedValue( - cachedStringValues, - name, - key -> { - String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(key); - String value = environment.getProperty(normalizedName, String.class); - if (value == null && normalizedName.equals("otel.exporter.otlp.protocol")) { - // SDK autoconfigure module defaults to `grpc`, but this module aligns with - // recommendation in specification to default to `http/protobuf` - return OtlpConfigUtil.PROTOCOL_HTTP_PROTOBUF; - } - return or(value, otelSdkProperties.getString(key)); - }); + String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(name); + String value = cachedEnvironment.getProperty(normalizedName, String.class); + if (value == null && normalizedName.equals("otel.exporter.otlp.protocol")) { + // SDK autoconfigure module defaults to `grpc`, but this module aligns with + // recommendation in specification to default to `http/protobuf` + return OtlpConfigUtil.PROTOCOL_HTTP_PROTOBUF; + } + return or(value, otelSdkProperties.getString(name)); } @Nullable @Override public Boolean getBoolean(String name) { - return getCachedValue( - cachedBooleanValues, - name, - key -> - or( - environment.getProperty( - ConfigUtil.normalizeEnvironmentVariableKey(key), Boolean.class), - otelSdkProperties.getBoolean(key))); + return or( + cachedEnvironment.getProperty( + ConfigUtil.normalizeEnvironmentVariableKey(name), Boolean.class), + otelSdkProperties.getBoolean(name)); } @Nullable @Override public Integer getInt(String name) { - return getCachedValue( - cachedIntValues, - name, - key -> - or( - environment.getProperty( - ConfigUtil.normalizeEnvironmentVariableKey(key), Integer.class), - otelSdkProperties.getInt(key))); + return or( + cachedEnvironment.getProperty( + ConfigUtil.normalizeEnvironmentVariableKey(name), Integer.class), + otelSdkProperties.getInt(name)); } @Nullable @Override public Long getLong(String name) { - return getCachedValue( - cachedLongValues, - name, - key -> - or( - environment.getProperty( - ConfigUtil.normalizeEnvironmentVariableKey(key), Long.class), - otelSdkProperties.getLong(key))); + return or( + cachedEnvironment.getProperty(ConfigUtil.normalizeEnvironmentVariableKey(name), Long.class), + otelSdkProperties.getLong(name)); } @Nullable @Override public Double getDouble(String name) { - return getCachedValue( - cachedDoubleValues, - name, - key -> - or( - environment.getProperty( - ConfigUtil.normalizeEnvironmentVariableKey(key), Double.class), - otelSdkProperties.getDouble(key))); + return or( + cachedEnvironment.getProperty( + ConfigUtil.normalizeEnvironmentVariableKey(name), Double.class), + otelSdkProperties.getDouble(name)); } @SuppressWarnings("unchecked") @Override public List getList(String name) { - return getCachedValue( - cachedListValues, - name, - key -> { - String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(key); + String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(name); - List list = listPropertyValues.get(normalizedName); - if (list != null) { - List c = customizedListProperties.getList(key); - if (!c.isEmpty()) { - return c; - } - if (!list.isEmpty()) { - return list; - } - } + List list = listPropertyValues.get(normalizedName); + if (list != null) { + List c = customizedListProperties.getList(name); + if (!c.isEmpty()) { + return c; + } + if (!list.isEmpty()) { + return list; + } + } - List envValue = - (List) environment.getProperty(normalizedName, List.class); - return or(envValue, otelSdkProperties.getList(key)); - }); + List envValue = + (List) cachedEnvironment.getProperty(normalizedName, List.class); + return or(envValue, otelSdkProperties.getList(name)); } @Nullable @Override public Duration getDuration(String name) { - return getCachedValue( - cachedDurationValues, - name, - key -> { - String value = getString(key); - if (value == null) { - return otelSdkProperties.getDuration(key); - } - return DefaultConfigProperties.createFromMap(Collections.singletonMap(key, value)) - .getDuration(key); - }); + String value = getString(name); + if (value == null) { + return otelSdkProperties.getDuration(name); + } + return DefaultConfigProperties.createFromMap(Collections.singletonMap(name, value)) + .getDuration(name); } @SuppressWarnings("unchecked") @Override public Map getMap(String name) { - return getCachedValue( - cachedMapValues, - name, - key -> { - Map otelSdkMap = otelSdkProperties.getMap(key); + Map otelSdkMap = otelSdkProperties.getMap(name); - String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(key); - // maps from config properties are not supported by Environment, so we have to fake it - Map specialMap = getSpecialMapProperty(normalizedName, otelSdkMap); - if (specialMap != null) { - return specialMap; - } + String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(name); + // maps from config properties are not supported by Environment, so we have to fake it + Map specialMap = getSpecialMapProperty(normalizedName, otelSdkMap); + if (specialMap != null) { + return specialMap; + } - String value = environment.getProperty(normalizedName); - if (value == null) { - return otelSdkMap; - } - return (Map) parser.parseExpression(value).getValue(); - }); + String value = cachedEnvironment.getProperty(normalizedName); + if (value == null) { + return otelSdkMap; + } + return (Map) parser.parseExpression(value).getValue(); } @Nullable diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigPropertiesTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigPropertiesTest.java index 61ea7ed3905a..c8afd35d5aa6 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigPropertiesTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigPropertiesTest.java @@ -196,7 +196,21 @@ public static Stream propertyCachingTestCases() { "otel.traces.sampler.arg=0.5", Double.class, (Consumer) - config -> assertThat(config.getDouble("otel.traces.sampler.arg")).isEqualTo(0.5))); + config -> assertThat(config.getDouble("otel.traces.sampler.arg")).isEqualTo(0.5)), + Arguments.of( + "otel.bsp.export.timeout=30s", + String.class, + (Consumer) + config -> + assertThat(config.getDuration("otel.bsp.export.timeout")) + .isEqualByComparingTo(java.time.Duration.ofSeconds(30))), + Arguments.of( + "otel.attribute.value.length.limit=256", + List.class, + (Consumer) + config -> + assertThat(config.getList("otel.attribute.value.length.limit")) + .containsExactly("256"))); } @ParameterizedTest @@ -230,6 +244,42 @@ void propertyCaching( }); } + @Test + @DisplayName("should cache map property lookups and call Environment.getProperty() only once") + void mapPropertyCaching() { + this.contextRunner + .withSystemProperties( + "otel.instrumentation.common.peer-service-mapping={'host1':'serviceA','host2':'serviceB'}") + .run( + context -> { + Environment realEnvironment = context.getBean("environment", Environment.class); + Environment spyEnvironment = spy(realEnvironment); + + SpringConfigProperties config = + new SpringConfigProperties( + spyEnvironment, + new SpelExpressionParser(), + context.getBean(OtlpExporterProperties.class), + context.getBean(OtelResourceProperties.class), + context.getBean(OtelSpringProperties.class), + DefaultConfigProperties.createFromMap(emptyMap())); + + for (int i = 0; i < 100; i++) { + Map mapping = + config.getMap("otel.instrumentation.common.peer-service-mapping"); + assertThat(mapping) + .containsEntry("host1", "serviceA") + .containsEntry("host2", "serviceB"); + } + + // Map properties call getProperty(name) which delegates to getProperty(name, + // String.class) + verify(spyEnvironment, times(1)) + .getProperty( + eq("otel.instrumentation.common.peer-service-mapping"), eq(String.class)); + }); + } + private static ConfigProperties getConfig(AssertableApplicationContext context) { return getConfig(context, emptyMap()); } From 77e4485b807432dad9149e50f36838320352c12d Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Tue, 4 Nov 2025 06:17:13 -0500 Subject: [PATCH 4/6] rename environment var --- .../properties/SpringConfigProperties.java | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java index ea760ca05198..aae84c2f7240 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/SpringConfigProperties.java @@ -25,7 +25,7 @@ * any time. */ public class SpringConfigProperties implements ConfigProperties { - private final CachedPropertyResolver cachedEnvironment; + private final CachedPropertyResolver environment; private final ExpressionParser parser; private final OtlpExporterProperties otlpExporterProperties; @@ -44,7 +44,7 @@ public SpringConfigProperties( OtelResourceProperties resourceProperties, OtelSpringProperties otelSpringProperties, ConfigProperties otelSdkProperties) { - this.cachedEnvironment = new CachedPropertyResolver(environment); + this.environment = new CachedPropertyResolver(environment); this.parser = parser; this.otlpExporterProperties = otlpExporterProperties; this.resourceProperties = resourceProperties; @@ -152,7 +152,7 @@ public static ConfigProperties create( @Override public String getString(String name) { String normalizedName = ConfigUtil.normalizeEnvironmentVariableKey(name); - String value = cachedEnvironment.getProperty(normalizedName, String.class); + String value = environment.getProperty(normalizedName, String.class); if (value == null && normalizedName.equals("otel.exporter.otlp.protocol")) { // SDK autoconfigure module defaults to `grpc`, but this module aligns with // recommendation in specification to default to `http/protobuf` @@ -165,8 +165,7 @@ public String getString(String name) { @Override public Boolean getBoolean(String name) { return or( - cachedEnvironment.getProperty( - ConfigUtil.normalizeEnvironmentVariableKey(name), Boolean.class), + environment.getProperty(ConfigUtil.normalizeEnvironmentVariableKey(name), Boolean.class), otelSdkProperties.getBoolean(name)); } @@ -174,8 +173,7 @@ public Boolean getBoolean(String name) { @Override public Integer getInt(String name) { return or( - cachedEnvironment.getProperty( - ConfigUtil.normalizeEnvironmentVariableKey(name), Integer.class), + environment.getProperty(ConfigUtil.normalizeEnvironmentVariableKey(name), Integer.class), otelSdkProperties.getInt(name)); } @@ -183,7 +181,7 @@ public Integer getInt(String name) { @Override public Long getLong(String name) { return or( - cachedEnvironment.getProperty(ConfigUtil.normalizeEnvironmentVariableKey(name), Long.class), + environment.getProperty(ConfigUtil.normalizeEnvironmentVariableKey(name), Long.class), otelSdkProperties.getLong(name)); } @@ -191,8 +189,7 @@ public Long getLong(String name) { @Override public Double getDouble(String name) { return or( - cachedEnvironment.getProperty( - ConfigUtil.normalizeEnvironmentVariableKey(name), Double.class), + environment.getProperty(ConfigUtil.normalizeEnvironmentVariableKey(name), Double.class), otelSdkProperties.getDouble(name)); } @@ -212,8 +209,7 @@ public List getList(String name) { } } - List envValue = - (List) cachedEnvironment.getProperty(normalizedName, List.class); + List envValue = (List) environment.getProperty(normalizedName, List.class); return or(envValue, otelSdkProperties.getList(name)); } @@ -240,7 +236,7 @@ public Map getMap(String name) { return specialMap; } - String value = cachedEnvironment.getProperty(normalizedName); + String value = environment.getProperty(normalizedName); if (value == null) { return otelSdkMap; } From 4dd742c410d4910da6e36215163755d97174ce1b Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Tue, 4 Nov 2025 07:11:59 -0500 Subject: [PATCH 5/6] revert ConditionalOnMissingBean --- .../OpenTelemetryAutoConfiguration.java | 1 - .../OpenTelemetryAutoConfigurationTest.java | 22 ------------------- 2 files changed, 23 deletions(-) diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java index 1d894e0330dc..b9a9bd7c9a2a 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java @@ -100,7 +100,6 @@ public ResourceProvider otelDistroVersionResourceProvider() { } @Bean - @ConditionalOnMissingBean public AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk( Environment env, OtlpExporterProperties otlpExporterProperties, diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java index af5ce6e12dc8..1e1a04bb5f10 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/test/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfigurationTest.java @@ -12,7 +12,6 @@ import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.exporter.otlp.internal.OtlpSpanExporterProvider; import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.internal.AutoConfigureListener; import io.opentelemetry.sdk.trace.export.SpanExporter; @@ -59,27 +58,6 @@ void customOpenTelemetry() { .hasBean("otelProperties")); } - private static class CustomAutoConfiguredSdkConfiguration { - @Bean - public AutoConfiguredOpenTelemetrySdk customAutoConfiguredSdk() { - return AutoConfiguredOpenTelemetrySdk.builder().build(); - } - } - - @Test - @DisplayName( - "when Application Context contains AutoConfiguredOpenTelemetrySdk bean should NOT use default") - void customAutoConfiguredOpenTelemetrySdk() { - this.contextRunner - .withUserConfiguration(CustomAutoConfiguredSdkConfiguration.class) - .withConfiguration(AutoConfigurations.of(OpenTelemetryAutoConfiguration.class)) - .run( - context -> - assertThat(context) - .hasBean("customAutoConfiguredSdk") - .doesNotHaveBean("autoConfiguredOpenTelemetrySdk")); - } - @Test @DisplayName( "when Application Context DOES NOT contain OpenTelemetry bean should initialize openTelemetry") From 00b526126fd16821af568dcbc7e192669914e57b Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Thu, 6 Nov 2025 16:49:32 -0500 Subject: [PATCH 6/6] cast differently --- .../internal/properties/CachedPropertyResolver.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/CachedPropertyResolver.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/CachedPropertyResolver.java index 8fa3d948303d..163df3348d07 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/CachedPropertyResolver.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/properties/CachedPropertyResolver.java @@ -40,13 +40,11 @@ final class CachedPropertyResolver { @Nullable T getProperty(String name, Class targetType) { CacheKey key = new CacheKey(name, targetType); - // CacheKey includes targetType, ensuring type match - @SuppressWarnings("unchecked") - Optional result = - (Optional) - cache.computeIfAbsent( - key, k -> Optional.ofNullable(environment.getProperty(name, targetType))); - return result.orElse(null); + return targetType.cast( + cache + .computeIfAbsent( + key, k -> Optional.ofNullable(environment.getProperty(name, targetType))) + .orElse(null)); } /**