From 5ce43bdfa21077fa4be764f7401f67c9e61d3644 Mon Sep 17 00:00:00 2001
From: Luccas Asaphe <167210535+LuccasAps@users.noreply.github.com>
Date: Sun, 26 Apr 2026 00:57:01 -0300
Subject: [PATCH 1/6] Add ObservationRegistry support to
ConfigClientRequestTemplateFactory
Closes #2972
Signed-off-by: Luccas Asaphe <167210535+LuccasAps@users.noreply.github.com>
---
spring-cloud-config-client/pom.xml | 5 ++
.../ConfigClientRequestTemplateFactory.java | 19 +++++
.../client/ConfigServerBootstrapper.java | 13 ++++
...onfigServerConfigDataLocationResolver.java | 4 +-
...nfigClientRequestTemplateFactoryTests.java | 77 +++++++++++++++++++
5 files changed, 117 insertions(+), 1 deletion(-)
create mode 100644 spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigClientRequestTemplateFactoryTests.java
diff --git a/spring-cloud-config-client/pom.xml b/spring-cloud-config-client/pom.xml
index c38d75265..2f92b8291 100644
--- a/spring-cloud-config-client/pom.xml
+++ b/spring-cloud-config-client/pom.xml
@@ -58,6 +58,11 @@
spring-boot-starter-actuator
true
+
+ org.springframework.boot
+ spring-boot-restclient
+ true
+
org.springframework.boot
spring-boot-starter-aspectj
diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientRequestTemplateFactory.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientRequestTemplateFactory.java
index 6ceca390f..e6926054a 100644
--- a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientRequestTemplateFactory.java
+++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientRequestTemplateFactory.java
@@ -26,6 +26,7 @@
import javax.net.ssl.SSLContext;
+import io.micrometer.observation.ObservationRegistry;
import org.apache.commons.logging.Log;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
@@ -35,6 +36,7 @@
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.util.Timeout;
+import org.springframework.boot.restclient.observation.ObservationRestTemplateCustomizer;
import org.springframework.cloud.configuration.SSLContextFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
@@ -44,6 +46,7 @@
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import org.springframework.http.client.observation.DefaultClientRequestObservationConvention;
import org.springframework.web.client.RestTemplate;
import static org.springframework.cloud.config.client.ConfigClientProperties.AUTHORIZATION;
@@ -54,9 +57,20 @@ public class ConfigClientRequestTemplateFactory {
private final ConfigClientProperties properties;
+ private final ObservationRegistry observationRegistry;
+
public ConfigClientRequestTemplateFactory(Log log, ConfigClientProperties properties) {
this.log = log;
this.properties = properties;
+ this.observationRegistry = ObservationRegistry.NOOP;
+
+ }
+
+ public ConfigClientRequestTemplateFactory(Log log, ConfigClientProperties properties,
+ ObservationRegistry observationRegistry) {
+ this.log = log;
+ this.properties = properties;
+ this.observationRegistry = observationRegistry;
}
public Log getLog() {
@@ -83,6 +97,11 @@ public RestTemplate create() {
template.setInterceptors(Arrays.asList(new GenericRequestHeaderInterceptor(headers)));
}
+ if (observationRegistry != null && observationRegistry != ObservationRegistry.NOOP) {
+ new ObservationRestTemplateCustomizer(observationRegistry, new DefaultClientRequestObservationConvention())
+ .customize(template);
+ }
+
return template;
}
diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerBootstrapper.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerBootstrapper.java
index a95a26275..721d114a7 100644
--- a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerBootstrapper.java
+++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerBootstrapper.java
@@ -19,6 +19,8 @@
import java.util.function.BiFunction;
import java.util.function.Function;
+import io.micrometer.observation.ObservationRegistry;
+
import org.springframework.boot.bootstrap.BootstrapContext;
import org.springframework.boot.bootstrap.BootstrapRegistry;
import org.springframework.boot.bootstrap.BootstrapRegistry.InstanceSupplier;
@@ -35,6 +37,8 @@ public class ConfigServerBootstrapper implements BootstrapRegistryInitializer {
private LoaderInterceptor loaderInterceptor;
+ private ObservationRegistry observationRegistry;
+
static ConfigServerBootstrapper create() {
return new ConfigServerBootstrapper();
}
@@ -46,6 +50,11 @@ public ConfigServerBootstrapper withRestTemplateFactory(
return this;
}
+ public ConfigServerBootstrapper withObservationRegistry(ObservationRegistry observationRegistry) {
+ this.observationRegistry = observationRegistry;
+ return this;
+ }
+
public ConfigServerBootstrapper withLoaderInterceptor(LoaderInterceptor loaderInterceptor) {
this.loaderInterceptor = loaderInterceptor;
return this;
@@ -53,6 +62,10 @@ public ConfigServerBootstrapper withLoaderInterceptor(LoaderInterceptor loaderIn
@Override
public void initialize(BootstrapRegistry registry) {
+
+ if (observationRegistry != null) {
+ registry.register(ObservationRegistry.class, InstanceSupplier.of(observationRegistry));
+ }
if (restTemplateFactory != null) {
registry.register(RestTemplate.class, restTemplateFactory::apply);
}
diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerConfigDataLocationResolver.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerConfigDataLocationResolver.java
index 5e94ab0a6..07fdd6387 100644
--- a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerConfigDataLocationResolver.java
+++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerConfigDataLocationResolver.java
@@ -21,6 +21,7 @@
import java.util.Properties;
import java.util.function.Supplier;
+import io.micrometer.observation.ObservationRegistry;
import org.apache.commons.logging.Log;
import org.springframework.boot.bootstrap.BootstrapRegistry;
@@ -241,7 +242,8 @@ public List resolveProfileSpecific(
event.getBootstrapContext().get(ConfigClientProperties.class)));
bootstrapContext.registerIfAbsent(ConfigClientRequestTemplateFactory.class,
- context -> new ConfigClientRequestTemplateFactory(log, context.get(ConfigClientProperties.class)));
+ context -> new ConfigClientRequestTemplateFactory(log, context.get(ConfigClientProperties.class),
+ context.getOrElse(ObservationRegistry.class, ObservationRegistry.NOOP)));
bootstrapContext.registerIfAbsent(RestTemplate.class, context -> {
ConfigClientRequestTemplateFactory factory = context.get(ConfigClientRequestTemplateFactory.class);
diff --git a/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigClientRequestTemplateFactoryTests.java b/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigClientRequestTemplateFactoryTests.java
new file mode 100644
index 000000000..7c6e7a32f
--- /dev/null
+++ b/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigClientRequestTemplateFactoryTests.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2026-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.config.client;
+
+import io.micrometer.observation.ObservationRegistry;
+import org.apache.commons.logging.LogFactory;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.web.client.RestTemplate;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Luccas Asaphe
+ *
+ */
+public class ConfigClientRequestTemplateFactoryTests {
+
+ @Test
+ void shouldInstrumentRestTemplateWhenObservationRegistryProvided() {
+ // 1. montar a factory
+ ConfigClientProperties properties = new ConfigClientProperties();
+ ObservationRegistry registry = ObservationRegistry.create();
+ ConfigClientRequestTemplateFactory factory = new ConfigClientRequestTemplateFactory(
+ LogFactory.getLog(getClass()), properties, registry);
+
+ // 2. criar o RestTemplate
+ RestTemplate restTemplate = factory.create();
+
+ // 3. verificar os interceptors
+ assertThat(restTemplate.getObservationRegistry()).isEqualTo(registry);
+ }
+
+ @Test
+ void shouldNotInstrumentRestTemplateWhenObservationRegistryNotProvided() {
+ // 1. montar a factory
+ ConfigClientProperties properties = new ConfigClientProperties();
+ ConfigClientRequestTemplateFactory factory = new ConfigClientRequestTemplateFactory(
+ LogFactory.getLog(getClass()), properties);
+
+ // 2. criar o RestTemplate
+ RestTemplate restTemplate = factory.create();
+
+ // 3. verificar os interceptors
+ assertThat(restTemplate.getObservationRegistry()).isEqualTo(ObservationRegistry.NOOP);
+ }
+
+ @Test
+ void shouldNotInstrumentRestTemplateWhenObservationRegistryIsNoop() {
+ // 1. montar a factory
+ ConfigClientProperties properties = new ConfigClientProperties();
+ ObservationRegistry registry = ObservationRegistry.NOOP;
+ ConfigClientRequestTemplateFactory factory = new ConfigClientRequestTemplateFactory(
+ LogFactory.getLog(getClass()), properties, ObservationRegistry.NOOP);
+
+ // 2. criar o RestTemplate
+ RestTemplate restTemplate = factory.create();
+
+ // 3. verificar os interceptors
+ assertThat(restTemplate.getObservationRegistry()).isEqualTo(ObservationRegistry.NOOP);
+ }
+
+}
From 4cecc0cfed5a32c6f0964f8d8f45054cec3a9a67 Mon Sep 17 00:00:00 2001
From: Luccas Asaphe <167210535+LuccasAps@users.noreply.github.com>
Date: Wed, 29 Apr 2026 01:24:39 -0300
Subject: [PATCH 2/6] Address review feedback
Signed-off-by: Luccas Asaphe <167210535+LuccasAps@users.noreply.github.com>
---
.../ConfigClientRequestTemplateFactory.java | 14 ++++++----
...nfigClientRequestTemplateFactoryTests.java | 20 ++++++-------
...nfigDataCustomizationIntegrationTests.java | 28 +++++++++++++++++++
3 files changed, 46 insertions(+), 16 deletions(-)
diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientRequestTemplateFactory.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientRequestTemplateFactory.java
index e6926054a..8beb1355d 100644
--- a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientRequestTemplateFactory.java
+++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientRequestTemplateFactory.java
@@ -57,12 +57,12 @@ public class ConfigClientRequestTemplateFactory {
private final ConfigClientProperties properties;
- private final ObservationRegistry observationRegistry;
+ private final ObservationRestTemplateCustomizer observationRestTemplateCustomizer;
public ConfigClientRequestTemplateFactory(Log log, ConfigClientProperties properties) {
this.log = log;
this.properties = properties;
- this.observationRegistry = ObservationRegistry.NOOP;
+ this.observationRestTemplateCustomizer = null;
}
@@ -70,7 +70,10 @@ public ConfigClientRequestTemplateFactory(Log log, ConfigClientProperties proper
ObservationRegistry observationRegistry) {
this.log = log;
this.properties = properties;
- this.observationRegistry = observationRegistry;
+ this.observationRestTemplateCustomizer = observationRegistry != ObservationRegistry.NOOP
+ ? new ObservationRestTemplateCustomizer(observationRegistry,
+ new DefaultClientRequestObservationConvention())
+ : null;
}
public Log getLog() {
@@ -97,9 +100,8 @@ public RestTemplate create() {
template.setInterceptors(Arrays.asList(new GenericRequestHeaderInterceptor(headers)));
}
- if (observationRegistry != null && observationRegistry != ObservationRegistry.NOOP) {
- new ObservationRestTemplateCustomizer(observationRegistry, new DefaultClientRequestObservationConvention())
- .customize(template);
+ if (observationRestTemplateCustomizer != null) {
+ observationRestTemplateCustomizer.customize(template);
}
return template;
diff --git a/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigClientRequestTemplateFactoryTests.java b/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigClientRequestTemplateFactoryTests.java
index 7c6e7a32f..6e7daed72 100644
--- a/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigClientRequestTemplateFactoryTests.java
+++ b/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigClientRequestTemplateFactoryTests.java
@@ -32,45 +32,45 @@ public class ConfigClientRequestTemplateFactoryTests {
@Test
void shouldInstrumentRestTemplateWhenObservationRegistryProvided() {
- // 1. montar a factory
+ // 1. set up the factory
ConfigClientProperties properties = new ConfigClientProperties();
ObservationRegistry registry = ObservationRegistry.create();
ConfigClientRequestTemplateFactory factory = new ConfigClientRequestTemplateFactory(
LogFactory.getLog(getClass()), properties, registry);
- // 2. criar o RestTemplate
+ // 2. create the RestTemplate
RestTemplate restTemplate = factory.create();
- // 3. verificar os interceptors
+ // 3. verify the observation registry
assertThat(restTemplate.getObservationRegistry()).isEqualTo(registry);
}
@Test
void shouldNotInstrumentRestTemplateWhenObservationRegistryNotProvided() {
- // 1. montar a factory
+ // 1. set up the factory
ConfigClientProperties properties = new ConfigClientProperties();
ConfigClientRequestTemplateFactory factory = new ConfigClientRequestTemplateFactory(
LogFactory.getLog(getClass()), properties);
- // 2. criar o RestTemplate
+ // 2. create the RestTemplate
RestTemplate restTemplate = factory.create();
- // 3. verificar os interceptors
+ // 3. verify the observation registry
assertThat(restTemplate.getObservationRegistry()).isEqualTo(ObservationRegistry.NOOP);
}
@Test
void shouldNotInstrumentRestTemplateWhenObservationRegistryIsNoop() {
- // 1. montar a factory
+ // 1. set up the factory
ConfigClientProperties properties = new ConfigClientProperties();
ObservationRegistry registry = ObservationRegistry.NOOP;
ConfigClientRequestTemplateFactory factory = new ConfigClientRequestTemplateFactory(
- LogFactory.getLog(getClass()), properties, ObservationRegistry.NOOP);
+ LogFactory.getLog(getClass()), properties, registry);
- // 2. criar o RestTemplate
+ // 2. create the RestTemplate
RestTemplate restTemplate = factory.create();
- // 3. verificar os interceptors
+ // 3. verify the observation registry
assertThat(restTemplate.getObservationRegistry()).isEqualTo(ObservationRegistry.NOOP);
}
diff --git a/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigServerConfigDataCustomizationIntegrationTests.java b/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigServerConfigDataCustomizationIntegrationTests.java
index 17aea6ed5..1f1f99549 100644
--- a/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigServerConfigDataCustomizationIntegrationTests.java
+++ b/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigServerConfigDataCustomizationIntegrationTests.java
@@ -18,6 +18,7 @@
import java.util.Optional;
+import io.micrometer.observation.ObservationRegistry;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
@@ -91,6 +92,33 @@ void customizableRestTemplate() {
}
}
+ @Test
+ void customizableObservationRegistry() {
+ ConfigurableApplicationContext context = null;
+ try {
+ ObservationRegistry registry = ObservationRegistry.create();
+ context = new SpringApplicationBuilder(TestConfig.class)
+ .addBootstrapRegistryInitializer(ConfigServerBootstrapper.create().withObservationRegistry(registry))
+ .addBootstrapRegistryInitializer(reg -> reg.addCloseListener(event -> {
+ BootstrapContext bootstrapContext = event.getBootstrapContext();
+ ConfigurableListableBeanFactory beanFactory = event.getApplicationContext().getBeanFactory();
+
+ RestTemplate restTemplate = bootstrapContext.get(RestTemplate.class);
+ beanFactory.registerSingleton("holder", new RestTemplateHolder(restTemplate));
+ }))
+ .run("--spring.config.import=optional:configserver:");
+
+ RestTemplateHolder holder = context.getBean(RestTemplateHolder.class);
+ assertThat(holder).isNotNull();
+ assertThat(holder.restTemplate.getObservationRegistry()).isEqualTo(registry);
+ }
+ finally {
+ if (context != null) {
+ context.close();
+ }
+ }
+ }
+
CustomRestTemplate restTemplate(BootstrapContext context) {
ConfigClientProperties properties = context.get(ConfigClientProperties.class);
String custom = context.get(Binder.class).bind("custom.prop", String.class).orElse("default-custom-prop");
From 4eb48eeb1fd2be8ef9edee5ec4ddc9fe57c546dc Mon Sep 17 00:00:00 2001
From: Luccas Asaphe <167210535+LuccasAps@users.noreply.github.com>
Date: Wed, 29 Apr 2026 02:20:55 -0300
Subject: [PATCH 3/6] Refactor to use subclasses for Micrometer observation
support
Signed-off-by: Luccas Asaphe <167210535+LuccasAps@users.noreply.github.com>
---
.../ConfigClientRequestTemplateFactory.java | 21 -------
.../client/ConfigServerBootstrapper.java | 13 -----
...onfigServerConfigDataLocationResolver.java | 11 ++--
...ionConfigClientRequestTemplateFactory.java | 55 +++++++++++++++++++
.../ObservationConfigServerBootstrapper.java | 44 +++++++++++++++
...nfigClientRequestTemplateFactoryTests.java | 4 +-
...nfigDataCustomizationIntegrationTests.java | 3 +-
7 files changed, 110 insertions(+), 41 deletions(-)
create mode 100644 spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ObservationConfigClientRequestTemplateFactory.java
create mode 100644 spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ObservationConfigServerBootstrapper.java
diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientRequestTemplateFactory.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientRequestTemplateFactory.java
index 8beb1355d..6ceca390f 100644
--- a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientRequestTemplateFactory.java
+++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigClientRequestTemplateFactory.java
@@ -26,7 +26,6 @@
import javax.net.ssl.SSLContext;
-import io.micrometer.observation.ObservationRegistry;
import org.apache.commons.logging.Log;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
@@ -36,7 +35,6 @@
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.util.Timeout;
-import org.springframework.boot.restclient.observation.ObservationRestTemplateCustomizer;
import org.springframework.cloud.configuration.SSLContextFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
@@ -46,7 +44,6 @@
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
-import org.springframework.http.client.observation.DefaultClientRequestObservationConvention;
import org.springframework.web.client.RestTemplate;
import static org.springframework.cloud.config.client.ConfigClientProperties.AUTHORIZATION;
@@ -57,23 +54,9 @@ public class ConfigClientRequestTemplateFactory {
private final ConfigClientProperties properties;
- private final ObservationRestTemplateCustomizer observationRestTemplateCustomizer;
-
public ConfigClientRequestTemplateFactory(Log log, ConfigClientProperties properties) {
this.log = log;
this.properties = properties;
- this.observationRestTemplateCustomizer = null;
-
- }
-
- public ConfigClientRequestTemplateFactory(Log log, ConfigClientProperties properties,
- ObservationRegistry observationRegistry) {
- this.log = log;
- this.properties = properties;
- this.observationRestTemplateCustomizer = observationRegistry != ObservationRegistry.NOOP
- ? new ObservationRestTemplateCustomizer(observationRegistry,
- new DefaultClientRequestObservationConvention())
- : null;
}
public Log getLog() {
@@ -100,10 +83,6 @@ public RestTemplate create() {
template.setInterceptors(Arrays.asList(new GenericRequestHeaderInterceptor(headers)));
}
- if (observationRestTemplateCustomizer != null) {
- observationRestTemplateCustomizer.customize(template);
- }
-
return template;
}
diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerBootstrapper.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerBootstrapper.java
index 721d114a7..a95a26275 100644
--- a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerBootstrapper.java
+++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerBootstrapper.java
@@ -19,8 +19,6 @@
import java.util.function.BiFunction;
import java.util.function.Function;
-import io.micrometer.observation.ObservationRegistry;
-
import org.springframework.boot.bootstrap.BootstrapContext;
import org.springframework.boot.bootstrap.BootstrapRegistry;
import org.springframework.boot.bootstrap.BootstrapRegistry.InstanceSupplier;
@@ -37,8 +35,6 @@ public class ConfigServerBootstrapper implements BootstrapRegistryInitializer {
private LoaderInterceptor loaderInterceptor;
- private ObservationRegistry observationRegistry;
-
static ConfigServerBootstrapper create() {
return new ConfigServerBootstrapper();
}
@@ -50,11 +46,6 @@ public ConfigServerBootstrapper withRestTemplateFactory(
return this;
}
- public ConfigServerBootstrapper withObservationRegistry(ObservationRegistry observationRegistry) {
- this.observationRegistry = observationRegistry;
- return this;
- }
-
public ConfigServerBootstrapper withLoaderInterceptor(LoaderInterceptor loaderInterceptor) {
this.loaderInterceptor = loaderInterceptor;
return this;
@@ -62,10 +53,6 @@ public ConfigServerBootstrapper withLoaderInterceptor(LoaderInterceptor loaderIn
@Override
public void initialize(BootstrapRegistry registry) {
-
- if (observationRegistry != null) {
- registry.register(ObservationRegistry.class, InstanceSupplier.of(observationRegistry));
- }
if (restTemplateFactory != null) {
registry.register(RestTemplate.class, restTemplateFactory::apply);
}
diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerConfigDataLocationResolver.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerConfigDataLocationResolver.java
index 07fdd6387..152b8a49a 100644
--- a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerConfigDataLocationResolver.java
+++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerConfigDataLocationResolver.java
@@ -21,7 +21,6 @@
import java.util.Properties;
import java.util.function.Supplier;
-import io.micrometer.observation.ObservationRegistry;
import org.apache.commons.logging.Log;
import org.springframework.boot.bootstrap.BootstrapRegistry;
@@ -241,9 +240,13 @@ public List resolveProfileSpecific(
.registerSingleton("configDataConfigClientProperties",
event.getBootstrapContext().get(ConfigClientProperties.class)));
- bootstrapContext.registerIfAbsent(ConfigClientRequestTemplateFactory.class,
- context -> new ConfigClientRequestTemplateFactory(log, context.get(ConfigClientProperties.class),
- context.getOrElse(ObservationRegistry.class, ObservationRegistry.NOOP)));
+ bootstrapContext.registerIfAbsent(ConfigClientRequestTemplateFactory.class, context -> {
+ ConfigClientProperties props = context.get(ConfigClientProperties.class);
+ if (ClassUtils.isPresent("io.micrometer.observation.ObservationRegistry", null)) {
+ return ObservationConfigClientRequestTemplateFactory.createWithObservation(context, log, props);
+ }
+ return new ConfigClientRequestTemplateFactory(log, props);
+ });
bootstrapContext.registerIfAbsent(RestTemplate.class, context -> {
ConfigClientRequestTemplateFactory factory = context.get(ConfigClientRequestTemplateFactory.class);
diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ObservationConfigClientRequestTemplateFactory.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ObservationConfigClientRequestTemplateFactory.java
new file mode 100644
index 000000000..8e3f58ce0
--- /dev/null
+++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ObservationConfigClientRequestTemplateFactory.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2026-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.config.client;
+
+import io.micrometer.observation.ObservationRegistry;
+import org.apache.commons.logging.Log;
+
+import org.springframework.boot.bootstrap.BootstrapContext;
+import org.springframework.boot.restclient.observation.ObservationRestTemplateCustomizer;
+import org.springframework.http.client.observation.DefaultClientRequestObservationConvention;
+import org.springframework.web.client.RestTemplate;
+
+public class ObservationConfigClientRequestTemplateFactory extends ConfigClientRequestTemplateFactory {
+
+ private final ObservationRestTemplateCustomizer observationRestTemplateCustomizer;
+
+ public ObservationConfigClientRequestTemplateFactory(Log log, ConfigClientProperties properties,
+ ObservationRegistry observationRegistry) {
+ super(log, properties);
+ this.observationRestTemplateCustomizer = observationRegistry != ObservationRegistry.NOOP
+ ? new ObservationRestTemplateCustomizer(observationRegistry,
+ new DefaultClientRequestObservationConvention())
+ : null;
+ }
+
+ @Override
+ public RestTemplate create() {
+ RestTemplate template = super.create();
+ if (observationRestTemplateCustomizer != null) {
+ observationRestTemplateCustomizer.customize(template);
+ }
+ return template;
+ }
+
+ static ConfigClientRequestTemplateFactory createWithObservation(BootstrapContext context, Log log,
+ ConfigClientProperties props) {
+ ObservationRegistry registry = context.getOrElse(ObservationRegistry.class, ObservationRegistry.NOOP);
+ return new ObservationConfigClientRequestTemplateFactory(log, props, registry);
+ }
+
+}
diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ObservationConfigServerBootstrapper.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ObservationConfigServerBootstrapper.java
new file mode 100644
index 000000000..a214ccd41
--- /dev/null
+++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ObservationConfigServerBootstrapper.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2026-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.config.client;
+
+import io.micrometer.observation.ObservationRegistry;
+
+import org.springframework.boot.bootstrap.BootstrapRegistry;
+
+public class ObservationConfigServerBootstrapper extends ConfigServerBootstrapper {
+
+ private ObservationRegistry observationRegistry;
+
+ static ObservationConfigServerBootstrapper create() {
+ return new ObservationConfigServerBootstrapper();
+ }
+
+ public ObservationConfigServerBootstrapper withObservationRegistry(ObservationRegistry observationRegistry) {
+ this.observationRegistry = observationRegistry;
+ return this;
+ }
+
+ @Override
+ public void initialize(BootstrapRegistry registry) {
+ super.initialize(registry);
+ if (observationRegistry != null) {
+ registry.register(ObservationRegistry.class, BootstrapRegistry.InstanceSupplier.of(observationRegistry));
+ }
+ }
+
+}
diff --git a/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigClientRequestTemplateFactoryTests.java b/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigClientRequestTemplateFactoryTests.java
index 6e7daed72..464d9fb27 100644
--- a/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigClientRequestTemplateFactoryTests.java
+++ b/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigClientRequestTemplateFactoryTests.java
@@ -35,7 +35,7 @@ void shouldInstrumentRestTemplateWhenObservationRegistryProvided() {
// 1. set up the factory
ConfigClientProperties properties = new ConfigClientProperties();
ObservationRegistry registry = ObservationRegistry.create();
- ConfigClientRequestTemplateFactory factory = new ConfigClientRequestTemplateFactory(
+ ObservationConfigClientRequestTemplateFactory factory = new ObservationConfigClientRequestTemplateFactory(
LogFactory.getLog(getClass()), properties, registry);
// 2. create the RestTemplate
@@ -64,7 +64,7 @@ void shouldNotInstrumentRestTemplateWhenObservationRegistryIsNoop() {
// 1. set up the factory
ConfigClientProperties properties = new ConfigClientProperties();
ObservationRegistry registry = ObservationRegistry.NOOP;
- ConfigClientRequestTemplateFactory factory = new ConfigClientRequestTemplateFactory(
+ ObservationConfigClientRequestTemplateFactory factory = new ObservationConfigClientRequestTemplateFactory(
LogFactory.getLog(getClass()), properties, registry);
// 2. create the RestTemplate
diff --git a/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigServerConfigDataCustomizationIntegrationTests.java b/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigServerConfigDataCustomizationIntegrationTests.java
index 1f1f99549..b207afa57 100644
--- a/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigServerConfigDataCustomizationIntegrationTests.java
+++ b/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigServerConfigDataCustomizationIntegrationTests.java
@@ -98,7 +98,8 @@ void customizableObservationRegistry() {
try {
ObservationRegistry registry = ObservationRegistry.create();
context = new SpringApplicationBuilder(TestConfig.class)
- .addBootstrapRegistryInitializer(ConfigServerBootstrapper.create().withObservationRegistry(registry))
+ .addBootstrapRegistryInitializer(
+ ObservationConfigServerBootstrapper.create().withObservationRegistry(registry))
.addBootstrapRegistryInitializer(reg -> reg.addCloseListener(event -> {
BootstrapContext bootstrapContext = event.getBootstrapContext();
ConfigurableListableBeanFactory beanFactory = event.getApplicationContext().getBeanFactory();
From 8097f33912875bc297be3fa70604499f6ded5508 Mon Sep 17 00:00:00 2001
From: Luccas Asaphe <167210535+LuccasAps@users.noreply.github.com>
Date: Wed, 29 Apr 2026 18:05:34 -0300
Subject: [PATCH 4/6] Add check for ObservationRestTemplateCustomizer
Signed-off-by: Luccas Asaphe <167210535+LuccasAps@users.noreply.github.com>
---
.../config/client/ConfigServerConfigDataLocationResolver.java | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerConfigDataLocationResolver.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerConfigDataLocationResolver.java
index 152b8a49a..9eb8e50df 100644
--- a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerConfigDataLocationResolver.java
+++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerConfigDataLocationResolver.java
@@ -242,7 +242,8 @@ public List resolveProfileSpecific(
bootstrapContext.registerIfAbsent(ConfigClientRequestTemplateFactory.class, context -> {
ConfigClientProperties props = context.get(ConfigClientProperties.class);
- if (ClassUtils.isPresent("io.micrometer.observation.ObservationRegistry", null)) {
+ if (ClassUtils.isPresent("io.micrometer.observation.ObservationRegistry", null) && ClassUtils
+ .isPresent("org.springframework.boot.restclient.observation.ObservationRestTemplateCustomizer", null)) {
return ObservationConfigClientRequestTemplateFactory.createWithObservation(context, log, props);
}
return new ConfigClientRequestTemplateFactory(log, props);
From f4e56271c93e08b73d2cf4f9b6150d6a805be949 Mon Sep 17 00:00:00 2001
From: Luccas Asaphe <167210535+LuccasAps@users.noreply.github.com>
Date: Thu, 30 Apr 2026 14:39:50 -0300
Subject: [PATCH 5/6] Add test for context startup without
spring-boot-restclient on classpath
Signed-off-by: Luccas Asaphe <167210535+LuccasAps@users.noreply.github.com>
---
.../client/ConfigServerBootstrapper.java | 2 +-
...onfigServerConfigDataLocationResolver.java | 2 +-
.../ObservationConfigServerBootstrapper.java | 2 +-
...erverConfigDataWithoutMicrometerTests.java | 52 +++++++++++++++++++
4 files changed, 55 insertions(+), 3 deletions(-)
create mode 100644 spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigServerConfigDataWithoutMicrometerTests.java
diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerBootstrapper.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerBootstrapper.java
index a95a26275..6f1d30e32 100644
--- a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerBootstrapper.java
+++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerBootstrapper.java
@@ -35,7 +35,7 @@ public class ConfigServerBootstrapper implements BootstrapRegistryInitializer {
private LoaderInterceptor loaderInterceptor;
- static ConfigServerBootstrapper create() {
+ public static ConfigServerBootstrapper create() {
return new ConfigServerBootstrapper();
}
diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerConfigDataLocationResolver.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerConfigDataLocationResolver.java
index 9eb8e50df..62111d566 100644
--- a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerConfigDataLocationResolver.java
+++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ConfigServerConfigDataLocationResolver.java
@@ -242,7 +242,7 @@ public List resolveProfileSpecific(
bootstrapContext.registerIfAbsent(ConfigClientRequestTemplateFactory.class, context -> {
ConfigClientProperties props = context.get(ConfigClientProperties.class);
- if (ClassUtils.isPresent("io.micrometer.observation.ObservationRegistry", null) && ClassUtils
+ if (ClassUtils
.isPresent("org.springframework.boot.restclient.observation.ObservationRestTemplateCustomizer", null)) {
return ObservationConfigClientRequestTemplateFactory.createWithObservation(context, log, props);
}
diff --git a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ObservationConfigServerBootstrapper.java b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ObservationConfigServerBootstrapper.java
index a214ccd41..5c8dbf7e7 100644
--- a/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ObservationConfigServerBootstrapper.java
+++ b/spring-cloud-config-client/src/main/java/org/springframework/cloud/config/client/ObservationConfigServerBootstrapper.java
@@ -24,7 +24,7 @@ public class ObservationConfigServerBootstrapper extends ConfigServerBootstrappe
private ObservationRegistry observationRegistry;
- static ObservationConfigServerBootstrapper create() {
+ public static ObservationConfigServerBootstrapper create() {
return new ObservationConfigServerBootstrapper();
}
diff --git a/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigServerConfigDataWithoutMicrometerTests.java b/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigServerConfigDataWithoutMicrometerTests.java
new file mode 100644
index 000000000..8f1848ba7
--- /dev/null
+++ b/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigServerConfigDataWithoutMicrometerTests.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2026-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.config.client;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.boot.SpringBootConfiguration;
+import org.springframework.boot.WebApplicationType;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.cloud.test.ClassPathExclusions;
+import org.springframework.context.ConfigurableApplicationContext;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Luccas Asaphe
+ *
+ */
+@ClassPathExclusions({ "spring-boot-starter-actuator-*.jar", "spring-boot-restclient-*.jar" })
+public class ConfigServerConfigDataWithoutMicrometerTests {
+
+ @Test
+ void contextStartsWithoutMicrometer() {
+ try (ConfigurableApplicationContext context = new SpringApplicationBuilder(TestConfig.class)
+ .web(WebApplicationType.NONE)
+ .run("--spring.config.import=optional:configserver:")) {
+ assertThat(context).isNotNull();
+ }
+ }
+
+ @SpringBootConfiguration
+ @EnableAutoConfiguration
+ static class TestConfig {
+
+ }
+
+}
From b41d10803476ba9bd64f3b03860830ecc2e60dcf Mon Sep 17 00:00:00 2001
From: Luccas Asaphe <167210535+LuccasAps@users.noreply.github.com>
Date: Fri, 1 May 2026 01:05:52 -0300
Subject: [PATCH 6/6] Update test to verify correct bean is created without
restclient on classpath
Signed-off-by: Luccas Asaphe <167210535+LuccasAps@users.noreply.github.com>
---
...nfigServerConfigDataWithoutMicrometerTests.java | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigServerConfigDataWithoutMicrometerTests.java b/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigServerConfigDataWithoutMicrometerTests.java
index 8f1848ba7..8281fc7d2 100644
--- a/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigServerConfigDataWithoutMicrometerTests.java
+++ b/spring-cloud-config-client/src/test/java/org/springframework/cloud/config/client/ConfigServerConfigDataWithoutMicrometerTests.java
@@ -18,9 +18,11 @@
import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.bootstrap.BootstrapContext;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.test.ClassPathExclusions;
import org.springframework.context.ConfigurableApplicationContext;
@@ -31,15 +33,25 @@
* @author Luccas Asaphe
*
*/
-@ClassPathExclusions({ "spring-boot-starter-actuator-*.jar", "spring-boot-restclient-*.jar" })
+@ClassPathExclusions({ "spring-boot-restclient-*.jar" })
public class ConfigServerConfigDataWithoutMicrometerTests {
@Test
void contextStartsWithoutMicrometer() {
try (ConfigurableApplicationContext context = new SpringApplicationBuilder(TestConfig.class)
.web(WebApplicationType.NONE)
+ .addBootstrapRegistryInitializer(registry -> registry.addCloseListener(event -> {
+ BootstrapContext bootstrapContext = event.getBootstrapContext();
+ ConfigurableListableBeanFactory beanFactory = event.getApplicationContext().getBeanFactory();
+
+ ConfigClientRequestTemplateFactory templateFactory = bootstrapContext
+ .get(ConfigClientRequestTemplateFactory.class);
+ beanFactory.registerSingleton("factory", templateFactory);
+ }))
.run("--spring.config.import=optional:configserver:")) {
assertThat(context).isNotNull();
+
+ assertThat(context.getBean("factory")).isNotInstanceOf(ObservationConfigClientRequestTemplateFactory.class);
}
}