diff --git a/spring/src/main/java/io/kubernetes/client/spring/extended/manifests/annotation/FromConfigMap.java b/spring/src/main/java/io/kubernetes/client/spring/extended/manifests/annotation/FromConfigMap.java index 240bee9aa1..731bc9db52 100644 --- a/spring/src/main/java/io/kubernetes/client/spring/extended/manifests/annotation/FromConfigMap.java +++ b/spring/src/main/java/io/kubernetes/client/spring/extended/manifests/annotation/FromConfigMap.java @@ -25,7 +25,8 @@ *
The annotations has to be be applied to member field of type Map The content of the map will be automatically updated at the interval specified by the property
- * "kubernetes.manifests.refreshInterval".
+ * "kubernetes.manifests.refreshInterval". The default polling getter reads the ConfigMap from the
+ * API server on every refresh.
*
* If the given configmap, is not present in the cluster, the content of the map will stay empty.
*/
diff --git a/spring/src/main/java/io/kubernetes/client/spring/extended/manifests/configmaps/PollingConfigMapGetter.java b/spring/src/main/java/io/kubernetes/client/spring/extended/manifests/configmaps/PollingConfigMapGetter.java
index d9249e29c2..a0db4eecda 100644
--- a/spring/src/main/java/io/kubernetes/client/spring/extended/manifests/configmaps/PollingConfigMapGetter.java
+++ b/spring/src/main/java/io/kubernetes/client/spring/extended/manifests/configmaps/PollingConfigMapGetter.java
@@ -12,14 +12,10 @@
*/
package io.kubernetes.client.spring.extended.manifests.configmaps;
-import com.github.benmanes.caffeine.cache.Cache;
-import com.github.benmanes.caffeine.cache.Caffeine;
-import io.kubernetes.client.apimachinery.NamespaceName;
import io.kubernetes.client.openapi.ApiClient;
import io.kubernetes.client.openapi.ApiException;
import io.kubernetes.client.openapi.apis.CoreV1Api;
import io.kubernetes.client.openapi.models.V1ConfigMap;
-import java.time.Duration;
import org.springframework.beans.factory.annotation.Autowired;
public class PollingConfigMapGetter implements ConfigMapGetter {
@@ -32,14 +28,10 @@ public class PollingConfigMapGetter implements ConfigMapGetter {
@Override
public V1ConfigMap get(String namespace, String name) {
CoreV1Api coreV1Api = new CoreV1Api(apiClient);
- return lastObservedConfigMap.get(
- new NamespaceName(namespace, name),
- k -> {
- try {
- return coreV1Api.readNamespacedConfigMap(name, namespace).execute();
- } catch (ApiException e) {
- throw new IllegalStateException(e);
- }
- });
+ try {
+ return coreV1Api.readNamespacedConfigMap(name, namespace).execute();
+ } catch (ApiException e) {
+ throw new IllegalStateException(e);
+ }
}
}
diff --git a/spring/src/test/java/io/kubernetes/client/spring/extended/manifests/KubernetesFromConfigMapPollingTest.java b/spring/src/test/java/io/kubernetes/client/spring/extended/manifests/KubernetesFromConfigMapPollingTest.java
new file mode 100644
index 0000000000..9828a79ce5
--- /dev/null
+++ b/spring/src/test/java/io/kubernetes/client/spring/extended/manifests/KubernetesFromConfigMapPollingTest.java
@@ -0,0 +1,98 @@
+/*
+Copyright 2026 The Kubernetes 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
+http://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 io.kubernetes.client.spring.extended.manifests;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Awaitility.await;
+
+import com.github.tomakehurst.wiremock.core.WireMockConfiguration;
+import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
+import io.kubernetes.client.openapi.ApiClient;
+import io.kubernetes.client.openapi.models.V1ConfigMap;
+import io.kubernetes.client.spring.extended.manifests.annotation.FromConfigMap;
+import io.kubernetes.client.spring.extended.manifests.config.KubernetesManifestsProperties;
+import java.time.Duration;
+import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.test.util.ReflectionTestUtils;
+
+class KubernetesFromConfigMapPollingTest {
+
+ private static final String CONFIG_MAP_PATH = "/api/v1/namespaces/default/configmaps/polling";
+
+ @RegisterExtension
+ static WireMockExtension apiServer =
+ WireMockExtension.newInstance()
+ .options(WireMockConfiguration.options().dynamicPort())
+ .build();
+
+ @Test
+ void refreshesFromApiServerAtConfiguredInterval() {
+ ApiClient apiClient = new ApiClient().setBasePath(apiServer.baseUrl());
+ KubernetesManifestsProperties properties =
+ new KubernetesManifestsProperties().setRefreshInterval(Duration.ofSeconds(1));
+ KubernetesFromConfigMapProcessor processor = new KubernetesFromConfigMapProcessor();
+
+ try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
+ context.registerBean(ApiClient.class, () -> apiClient);
+ context.registerBean(KubernetesManifestsProperties.class, () -> properties);
+ context.refresh();
+ context.getAutowireCapableBeanFactory().autowireBean(processor);
+ processor.setApplicationContext(context);
+
+ stubConfigMap(
+ apiClient,
+ new V1ConfigMap().putDataItem("foo", "original").putDataItem("removed", "value"));
+ MyBean bean = new MyBean();
+ processor.postProcessAfterInitialization(bean, "myBean");
+ assertThat(bean.data).containsEntry("foo", "original").containsKey("removed");
+ apiServer.verify(getRequestedFor(urlPathEqualTo(CONFIG_MAP_PATH)));
+
+ apiServer.resetRequests();
+ stubConfigMap(
+ apiClient, new V1ConfigMap().putDataItem("foo", "updated").putDataItem("added", "value"));
+ await()
+ .atMost(properties.getRefreshInterval().multipliedBy(5))
+ .untilAsserted(
+ () ->
+ assertThat(bean.data)
+ .containsExactlyInAnyOrderEntriesOf(
+ Map.of("foo", "updated", "added", "value")));
+ apiServer.verify(getRequestedFor(urlPathEqualTo(CONFIG_MAP_PATH)));
+ } finally {
+ ((ScheduledExecutorService) ReflectionTestUtils.getField(processor, "configMapKeyRefresher"))
+ .shutdownNow();
+ }
+ }
+
+ private static void stubConfigMap(ApiClient apiClient, V1ConfigMap configMap) {
+ apiServer.stubFor(
+ get(urlPathEqualTo(CONFIG_MAP_PATH))
+ .willReturn(
+ aResponse()
+ .withHeader("Content-Type", "application/json")
+ .withBody(apiClient.getJSON().serialize(configMap))));
+ }
+
+ static class MyBean {
+ @FromConfigMap(namespace = "default", name = "polling")
+ private Map