diff --git a/docs/modules/ROOT/pages/spring-cloud-commons/loadbalancer.adoc b/docs/modules/ROOT/pages/spring-cloud-commons/loadbalancer.adoc
index 6468a80f..56926206 100644
--- a/docs/modules/ROOT/pages/spring-cloud-commons/loadbalancer.adoc
+++ b/docs/modules/ROOT/pages/spring-cloud-commons/loadbalancer.adoc
@@ -204,7 +204,7 @@ WARNING: If using any of the Service Discovery-backed suppliers, adding this hea
from the Service Registry.
TIP: The `HealthCheckServiceInstanceListSupplier` relies on having updated instances provided by a delegate flux. In the rare cases when you want to use a delegate that does not refresh the instances, even though the list of instances may change (such as the `DiscoveryClientServiceInstanceListSupplier` provided by us), you can set `spring.cloud.loadbalancer.health-check.refetch-instances` to `true` to have the instance list refreshed by the `HealthCheckServiceInstanceListSupplier`. You can then also adjust the refretch intervals by modifying the value of `spring.cloud.loadbalancer.health-check.refetch-instances-interval` and opt to disable the additional healthcheck repetitions by setting `spring.cloud.loadbalancer.health-check.repeat-health-check` to `false` as every instances refetch
- will also trigger a healthcheck.
+will also trigger a healthcheck.
`HealthCheckServiceInstanceListSupplier` uses properties prefixed with
`spring.cloud.loadbalancer.health-check`. You can set the `initialDelay` and `interval`
@@ -549,8 +549,41 @@ NOTE: For the properties where maps where already used, where you can specify a
NOTE: Starting with `4.1.0`, we have introduced the `callGetWithRequestOnDelegates` flag in `LoadBalancerProperties`. If this flag is set to `true`, `ServiceInstanceListSupplier#get(Request request)` method will be implemented to call `delegate.get(request)` in classes assignable from `DelegatingServiceInstanceListSupplier` that don't already implement that method, with the exclusion of `CachingServiceInstanceListSupplier` and `HealthCheckServiceInstanceListSupplier`, which should be placed in the instance supplier hierarchy directly after the supplier performing instance retrieval over the network, before any request-based filtering is done. It is set to `true` by default.
-[[-aot-and-native-image-support]]
+[[aot-and-native-image-support]]
== AOT and Native Image Support
Since `4.0.0`, Spring Cloud LoadBalancer supports Spring AOT transformations and native images. However, to use this feature, you need to explicitly define your `LoadBalancerClient` service IDs. You can do so by using the `value` or `name` attributes of the `@LoadBalancerClient` annotation or as values of the `spring.cloud.loadbalancer.eager-load.clients` property.
+== LoadBalancer Integration for Spring Interface Clients AutoConfiguration
+
+Since `5.0.0`, Spring Cloud LoadBalancer supports https://docs.spring.io/spring-framework/reference/7.0-SNAPSHOT/integration/rest-clients.html#rest-http-interface[Spring Interface Clients] AutoConfiguration through the `LoadBalancerRestClientHttpServiceGroupConfigurer` and `LoadBalancerWebClientHttpServiceGroupConfigurer`.
+
+For each Interface Client group, if the group `baseUrl` (defined under the
+`spring.http.client.service.group.[groupName].base-url` property) is `null`, a `serviceId`-based URL for load-balancing is set up as the `baseUrl`, with `serviceId` resolved from the Interface Client `groupName`.
+
+If the group `baseUrl` is `null` or its scheme is set to `lb`, a
+`DeferringLoadBalancerInterceptor` instance is picked from the application context for blocking scenarios, and a `DeferringLoadBalancerExchangeFilterFunction` instance for reactive scenarios, and is added to the group's `RestClient.Builder` or `WebClient.Builder` if available, allowing for the requests to be load-balanced.
+
+For example, in an app with the following Interface Clients configuration:
+
+[source,java,indent=0]
+----
+@SpringBootApplication
+@ImportHttpServices(group = "verificationClient", types = {VerificationService.class,
+ PersonService.class})
+public class HttpVerificationClientApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(HttpVerificationClientApplication.class, args);
+ }
+}
+----
+
+If the `spring.http.client.service.group.verificationClient.base-url` property is not set, it will be automatically set to `http://verificationClient`.
+The default scheme (`http`) is used initially; however, if a secure `ServiceInstance` is selected through load-balancing, it will be changed to `https`.
+
+If the `spring.http.client.service.group.verificationClient.base-url` property is set to a URL that has the `lb` scheme, (for example, `lb://verificationClient/path`), it will be used with `http` being initially set as the default scheme.
+If a secure `ServiceInstance` is selected through load-balancing, the scheme will be changed to `https`.
+In both of these cases, either a `DeferringLoadBalancerInterceptor` or `DeferringLoadBalancerExchangeFilterFunction` will be added to the group's client builder, enabling the requests to be load-balanced.
+
+If the `spring.http.client.service.group.verificationClient.base-url` property is set to a URL that does not have the scheme set to `lb`, (for example, `lb://verificationClient/path`), no load-balancer integration will be applied.
diff --git a/docs/modules/ROOT/partials/_configprops.adoc b/docs/modules/ROOT/partials/_configprops.adoc
index 56de35e0..eb6f9da0 100644
--- a/docs/modules/ROOT/partials/_configprops.adoc
+++ b/docs/modules/ROOT/partials/_configprops.adoc
@@ -1,7 +1,7 @@
|===
|Name | Default | Description
-|spring.cloud.compatibility-verifier.compatible-boot-versions | `+++3.5.x+++` | Default accepted versions for the Spring Boot dependency. You can set {@code x} for the patch version if you don't want to specify a concrete value. Example: {@code 3.5.x}
+|spring.cloud.compatibility-verifier.compatible-boot-versions | `+++4.0.x+++` | Default accepted versions for the Spring Boot dependency. You can set {@code x} for the patch version if you don't want to specify a concrete value. Example: {@code 3.5.x}
|spring.cloud.compatibility-verifier.enabled | `+++false+++` | Enables creation of Spring Cloud compatibility verification.
|spring.cloud.config.allow-override | `+++true+++` | Flag to indicate that {@link #isOverrideSystemProperties() systemPropertiesOverride} can be used. Set to false to prevent users from changing the default accidentally. Default true.
|spring.cloud.config.initialize-on-context-refresh | `+++false+++` | Flag to initialize bootstrap configuration on context refresh event. Default false.
diff --git a/docs/pom.xml b/docs/pom.xml
index 652c67b6..cf7b1cb4 100644
--- a/docs/pom.xml
+++ b/docs/pom.xml
@@ -8,7 +8,7 @@
org.springframework.cloudspring-cloud-commons-parent
- 4.3.1-SNAPSHOT
+ 5.0.0-SNAPSHOTjarSpring Cloud Commons Docs
diff --git a/pom.xml b/pom.xml
index 285ba22d..f16fdfd6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
4.0.0org.springframework.cloudspring-cloud-commons-parent
- 4.3.1-SNAPSHOT
+ 5.0.0-SNAPSHOTpomSpring Cloud Commons ParentSpring Cloud Commons Parent
@@ -13,7 +13,7 @@
org.springframework.cloudspring-cloud-build
- 4.3.1-SNAPSHOT
+ 5.0.0-SNAPSHOT
diff --git a/spring-cloud-commons-dependencies/pom.xml b/spring-cloud-commons-dependencies/pom.xml
index 954fc246..766bf89a 100644
--- a/spring-cloud-commons-dependencies/pom.xml
+++ b/spring-cloud-commons-dependencies/pom.xml
@@ -6,11 +6,11 @@
spring-cloud-dependencies-parentorg.springframework.cloud
- 4.3.1-SNAPSHOT
+ 5.0.0-SNAPSHOTspring-cloud-commons-dependencies
- 4.3.1-SNAPSHOT
+ 5.0.0-SNAPSHOTpomspring-cloud-commons-dependenciesSpring Cloud Commons Dependencies
diff --git a/spring-cloud-commons/pom.xml b/spring-cloud-commons/pom.xml
index 3b84ae5c..f316a3e6 100644
--- a/spring-cloud-commons/pom.xml
+++ b/spring-cloud-commons/pom.xml
@@ -7,7 +7,7 @@
org.springframework.cloudspring-cloud-commons-parent
- 4.3.1-SNAPSHOT
+ 5.0.0-SNAPSHOT..spring-cloud-commons
diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration.java
index 6f7ed529..a682c4b0 100644
--- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration.java
+++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerAutoConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2024 the original author or authors.
+ * Copyright 2012-2025 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.
@@ -30,6 +30,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.http.client.service.HttpClientServiceProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
@@ -110,6 +111,15 @@ public class LoadBalancerAutoConfiguration {
return new LoadBalancerRestTemplateBuilderBeanPostProcessor<>(loadBalancerInterceptorProvider, context);
}
+ @Bean
+ @ConditionalOnBean({ HttpClientServiceProperties.class, ReactiveLoadBalancer.Factory.class })
+ @ConditionalOnMissingBean(LoadBalancerRestClientHttpServiceGroupConfigurer.class)
+ LoadBalancerRestClientHttpServiceGroupConfigurer loadBalancerRestClientHttpServiceGroupConfigurer(
+ ObjectProvider loadBalancerInterceptorProvider,
+ HttpClientServiceProperties properties) {
+ return new LoadBalancerRestClientHttpServiceGroupConfigurer(loadBalancerInterceptorProvider, properties);
+ }
+
}
@AutoConfiguration
diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java
index 49a0d80a..4057de50 100644
--- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java
+++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2024 the original author or authors.
+ * Copyright 2012-2025 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.
@@ -145,8 +145,7 @@ public class LoadBalancerProperties {
this.hintHeaderName = hintHeaderName;
}
- // TODO: fix spelling in a major release
- public void setxForwarded(XForwarded xForwarded) {
+ public void setXForwarded(XForwarded xForwarded) {
this.xForwarded = xForwarded;
}
diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerRestClientHttpServiceGroupConfigurer.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerRestClientHttpServiceGroupConfigurer.java
new file mode 100644
index 00000000..bcedc405
--- /dev/null
+++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerRestClientHttpServiceGroupConfigurer.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2012-2025 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.client.loadbalancer;
+
+import java.net.URI;
+
+import org.jspecify.annotations.NonNull;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.http.client.service.HttpClientServiceProperties;
+import org.springframework.util.function.SingletonSupplier;
+import org.springframework.web.client.RestClient;
+import org.springframework.web.client.support.RestClientHttpServiceGroupConfigurer;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import static org.springframework.cloud.client.loadbalancer.LoadBalancerUriTools.DEFAULT_SCHEME;
+import static org.springframework.cloud.client.loadbalancer.LoadBalancerUriTools.constructInterfaceClientsBaseUrl;
+
+/**
+ * Load-balancer-specific {@link RestClientHttpServiceGroupConfigurer} implementation. If
+ * the group {@code baseUrl} is {@code null}, sets up a {@code baseUrl} with LoadBalancer
+ * {@code serviceId} -resolved from Interface Client {@code groupName} set as
+ * {@code host}. If the group {@code baseUrl} is {@code null} or has {@code lb} set as its
+ * scheme, a {@link DeferringLoadBalancerInterceptor} instance picked from application
+ * context is added to the group's {@link RestClient.Builder} if available, allowing for
+ * the requests to be load-balanced.
+ *
+ * @author Olga Maciaszek-Sharma
+ * @since 5.0.0
+ * @see RestClientHttpServiceGroupConfigurer
+ * @see HttpClientServiceProperties
+ */
+public class LoadBalancerRestClientHttpServiceGroupConfigurer implements RestClientHttpServiceGroupConfigurer {
+
+ // Make sure Boot's configurers run before
+ private static final int ORDER = 10;
+
+ private final SingletonSupplier loadBalancerInterceptorSupplier;
+
+ private final HttpClientServiceProperties clientServiceProperties;
+
+ public LoadBalancerRestClientHttpServiceGroupConfigurer(
+ ObjectProvider loadBalancerInterceptorProvider,
+ HttpClientServiceProperties clientServiceProperties) {
+ this.loadBalancerInterceptorSupplier = SingletonSupplier
+ .ofNullable(loadBalancerInterceptorProvider::getIfAvailable);
+ this.clientServiceProperties = clientServiceProperties;
+ }
+
+ @Override
+ public void configureGroups(@NonNull Groups groups) {
+ DeferringLoadBalancerInterceptor loadBalancerInterceptor = loadBalancerInterceptorSupplier.get();
+ if (loadBalancerInterceptor == null) {
+ throw new IllegalStateException(
+ DeferringLoadBalancerInterceptor.class.getSimpleName() + " bean not available.");
+ }
+
+ groups.forEachGroup((group, clientBuilder, factoryBuilder) -> {
+ String groupName = group.name();
+ HttpClientServiceProperties.Group groupProperties = clientServiceProperties.getGroup().get(groupName);
+ String baseUrlString = groupProperties == null ? null : groupProperties.getBaseUrl();
+ URI existingBaseUrl = baseUrlString == null ? null : URI.create(baseUrlString);
+ if (existingBaseUrl == null) {
+ URI baseUrl = constructBaseUrl(groupName);
+ clientBuilder.baseUrl(baseUrl);
+ clientBuilder.requestInterceptor(loadBalancerInterceptor);
+ }
+ else if ("lb".equalsIgnoreCase(existingBaseUrl.getScheme())) {
+ URI baseUrl = UriComponentsBuilder.fromUri(existingBaseUrl).scheme(DEFAULT_SCHEME).build().toUri();
+ clientBuilder.baseUrl(baseUrl);
+ clientBuilder.requestInterceptor(loadBalancerInterceptor);
+ }
+ });
+ }
+
+ @Override
+ public int getOrder() {
+ return ORDER;
+ }
+
+ private URI constructBaseUrl(String groupName) {
+ return constructInterfaceClientsBaseUrl(groupName);
+ }
+
+}
diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerUriTools.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerUriTools.java
index 7715875d..d25cff78 100644
--- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerUriTools.java
+++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerUriTools.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2025 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.
@@ -37,12 +37,15 @@ public final class LoadBalancerUriTools {
private static final String PERCENTAGE_SIGN = "%";
- private static final String DEFAULT_SCHEME = "http";
-
private static final String DEFAULT_SECURE_SCHEME = "https";
private static final Map INSECURE_SCHEME_MAPPINGS;
+ /**
+ * Default scheme.
+ */
+ public static final String DEFAULT_SCHEME = "http";
+
static {
INSECURE_SCHEME_MAPPINGS = new HashMap<>();
INSECURE_SCHEME_MAPPINGS.put(DEFAULT_SCHEME, DEFAULT_SECURE_SCHEME);
@@ -115,4 +118,8 @@ public final class LoadBalancerUriTools {
return originalOrDefault;
}
+ public static URI constructInterfaceClientsBaseUrl(String groupName) {
+ return UriComponentsBuilder.newInstance().scheme(DEFAULT_SCHEME).host(groupName).encode().build().toUri();
+ }
+
}
diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RequestData.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RequestData.java
index 8c320190..2bd21ab6 100644
--- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RequestData.java
+++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/RequestData.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2025 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.
@@ -29,6 +29,7 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.client.ClientRequest;
@@ -80,7 +81,7 @@ public class RequestData {
}
private static MultiValueMap buildCookies(MultiValueMap cookies) {
- HttpHeaders newCookies = new HttpHeaders();
+ MultiValueMap newCookies = new LinkedMultiValueMap<>();
if (cookies != null) {
cookies.forEach((key, value) -> value
.forEach(cookie -> newCookies.put(cookie.getName(), Collections.singletonList(cookie.getValue()))));
@@ -89,7 +90,7 @@ public class RequestData {
}
private static MultiValueMap buildCookiesFromHeaders(HttpHeaders headers) {
- HttpHeaders newCookies = new HttpHeaders();
+ MultiValueMap newCookies = new LinkedMultiValueMap<>();
if (headers == null) {
return newCookies;
}
diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerBeanPostProcessorAutoConfiguration.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerBeanPostProcessorAutoConfiguration.java
index f755f95d..58913842 100644
--- a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerBeanPostProcessorAutoConfiguration.java
+++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerBeanPostProcessorAutoConfiguration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2024 the original author or authors.
+ * Copyright 2012-2025 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.
@@ -21,6 +21,8 @@ import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.http.client.reactive.service.ReactiveHttpClientServiceProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.context.ApplicationContext;
@@ -66,6 +68,15 @@ public class LoadBalancerBeanPostProcessorAutoConfiguration {
return new DeferringLoadBalancerExchangeFilterFunction<>(exchangeFilterFunctionProvider);
}
+ @Bean
+ @ConditionalOnBean({ ReactiveHttpClientServiceProperties.class, ReactiveLoadBalancer.Factory.class })
+ @ConditionalOnMissingBean(LoadBalancerWebClientHttpServiceGroupConfigurer.class)
+ LoadBalancerWebClientHttpServiceGroupConfigurer loadBalancerWebClientHttpServiceGroupConfigurer(
+ ObjectProvider> deferringExchangeFilterFunction,
+ ReactiveHttpClientServiceProperties properties) {
+ return new LoadBalancerWebClientHttpServiceGroupConfigurer(deferringExchangeFilterFunction, properties);
+ }
+
}
static final class OnAnyLoadBalancerImplementationPresentCondition extends AnyNestedCondition {
diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerWebClientHttpServiceGroupConfigurer.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerWebClientHttpServiceGroupConfigurer.java
new file mode 100644
index 00000000..bba085d9
--- /dev/null
+++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerWebClientHttpServiceGroupConfigurer.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2012-2025 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.client.loadbalancer.reactive;
+
+import java.net.URI;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.http.client.reactive.service.ReactiveHttpClientServiceProperties;
+import org.springframework.boot.autoconfigure.http.client.service.HttpClientServiceProperties;
+import org.springframework.util.function.SingletonSupplier;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.support.WebClientHttpServiceGroupConfigurer;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import static org.springframework.cloud.client.loadbalancer.LoadBalancerUriTools.DEFAULT_SCHEME;
+import static org.springframework.cloud.client.loadbalancer.LoadBalancerUriTools.constructInterfaceClientsBaseUrl;
+
+/**
+ * Load-balancer-specific {@link WebClientHttpServiceGroupConfigurer} implementation. If
+ * the group {@code baseUrl} is {@code null}, sets up a {@code baseUrl} with LoadBalancer
+ * {@code serviceId} -resolved from Interface Client {@code groupName} set as
+ * {@code host}. If the group {@code baseUrl} is {@code null} or has {@code lb} set as its
+ * scheme, a {@link DeferringLoadBalancerExchangeFilterFunction} instance picked from
+ * application context is added to the group's {@link WebClient.Builder} if available,
+ * allowing for the requests to be load-balanced.
+ *
+ * @author Olga Maciaszek-Sharma
+ * @since 5.0.0
+ * @see WebClient.Builder
+ * @see HttpClientServiceProperties
+ */
+public class LoadBalancerWebClientHttpServiceGroupConfigurer implements WebClientHttpServiceGroupConfigurer {
+
+ // Make sure Boot's configurers run before
+ private static final int ORDER = 10;
+
+ private final SingletonSupplier> loadBalancerFilterFunctionSupplier;
+
+ private final ReactiveHttpClientServiceProperties clientServiceProperties;
+
+ public LoadBalancerWebClientHttpServiceGroupConfigurer(
+ ObjectProvider> exchangeFilterFunctionProvider,
+ ReactiveHttpClientServiceProperties clientServiceProperties) {
+ this.loadBalancerFilterFunctionSupplier = SingletonSupplier
+ .ofNullable(exchangeFilterFunctionProvider::getIfAvailable);
+ this.clientServiceProperties = clientServiceProperties;
+ }
+
+ @Override
+ public void configureGroups(Groups groups) {
+ DeferringLoadBalancerExchangeFilterFunction loadBalancerFilterFunction = loadBalancerFilterFunctionSupplier
+ .get();
+ if (loadBalancerFilterFunction == null) {
+ throw new IllegalStateException(
+ DeferringLoadBalancerExchangeFilterFunction.class.getSimpleName() + " bean not available.");
+ }
+ groups.forEachGroup((group, clientBuilder, factoryBuilder) -> {
+ String groupName = group.name();
+ ReactiveHttpClientServiceProperties.Group groupProperties = clientServiceProperties.getGroup()
+ .get(groupName);
+ String baseUrlString = groupProperties == null ? null : groupProperties.getBaseUrl();
+ URI existingBaseUrl = baseUrlString == null ? null : URI.create(baseUrlString);
+ if (existingBaseUrl == null) {
+ URI baseUrl = constructBaseUrl(groupName);
+ clientBuilder.baseUrl(String.valueOf(baseUrl));
+ clientBuilder.filter(loadBalancerFilterFunction);
+ }
+ else if ("lb".equalsIgnoreCase(existingBaseUrl.getScheme())) {
+ String baseUrl = UriComponentsBuilder.fromUri(existingBaseUrl)
+ .scheme(DEFAULT_SCHEME)
+ .build()
+ .toUriString();
+ clientBuilder.baseUrl(baseUrl);
+ clientBuilder.filter(loadBalancerFilterFunction);
+ }
+ });
+ }
+
+ @Override
+ public int getOrder() {
+ return ORDER;
+ }
+
+ private URI constructBaseUrl(String groupName) {
+ return constructInterfaceClientsBaseUrl(groupName);
+ }
+
+}
diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityVerifierProperties.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityVerifierProperties.java
index a5663ab7..27cb50da 100644
--- a/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityVerifierProperties.java
+++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/CompatibilityVerifierProperties.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2025 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.
@@ -22,6 +22,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Marcin Grzejszczak
+ * @author Olga Maciaszek-Sharma
*/
@ConfigurationProperties("spring.cloud.compatibility-verifier")
public class CompatibilityVerifierProperties {
@@ -36,7 +37,7 @@ public class CompatibilityVerifierProperties {
* the patch version if you don't want to specify a concrete value. Example:
* {@code 3.5.x}
*/
- private List compatibleBootVersions = List.of("3.5.x");
+ private List compatibleBootVersions = List.of("4.0.x");
public boolean isEnabled() {
return this.enabled;
diff --git a/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/SpringBootVersionVerifier.java b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/SpringBootVersionVerifier.java
index 880f49f9..f1e6cb69 100644
--- a/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/SpringBootVersionVerifier.java
+++ b/spring-cloud-commons/src/main/java/org/springframework/cloud/configuration/SpringBootVersionVerifier.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2025 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.
@@ -27,7 +27,7 @@ import org.springframework.boot.SpringBootVersion;
import org.springframework.util.StringUtils;
/**
- * Verifies if Spring Boot has proper version.
+ * Verifies if Spring Boot has the proper version.
*/
class SpringBootVersionVerifier implements CompatibilityVerifier {
@@ -35,7 +35,7 @@ class SpringBootVersionVerifier implements CompatibilityVerifier {
final Map ACCEPTED_VERSIONS = new HashMap<>() {
{
- this.put("3.5", is3_5());
+ this.put("4.0", is4_0());
}
};
@@ -70,12 +70,12 @@ class SpringBootVersionVerifier implements CompatibilityVerifier {
return SpringBootVersion.getVersion();
}
- CompatibilityPredicate is3_5() {
+ CompatibilityPredicate is4_0() {
return new CompatibilityPredicate() {
@Override
public String toString() {
- return "Predicate for Boot 3.5";
+ return "Predicate for Boot 4.0";
}
@Override
diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/AbstractLoadBalancerAutoConfigurationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/AbstractLoadBalancerAutoConfigurationTests.java
index 2a387edd..abbee725 100644
--- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/AbstractLoadBalancerAutoConfigurationTests.java
+++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/AbstractLoadBalancerAutoConfigurationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2024 the original author or authors.
+ * Copyright 2012-2025 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.
@@ -25,9 +25,11 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.autoconfigure.http.client.service.HttpClientServiceProperties;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.client.DefaultServiceInstance;
import org.springframework.cloud.client.ServiceInstance;
+import org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerWebClientHttpServiceGroupConfigurer;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -154,6 +156,14 @@ public abstract class AbstractLoadBalancerAutoConfigurationTests {
});
}
+ @Test
+ void loadBalancerRestClientHttpServiceGroupConfigurerPresent() {
+ applicationContextRunner.withUserConfiguration(OneRestClientBuilder.class).run(context -> {
+ assertThat(context.getBeansOfType(LoadBalancerRestClientHttpServiceGroupConfigurer.class)).hasSize(1);
+ assertThat(context.getBeansOfType(LoadBalancerWebClientHttpServiceGroupConfigurer.class)).hasSize(0);
+ });
+ }
+
protected abstract void assertLoadBalanced(RestClient.Builder restClientBuilder);
protected abstract void assertLoadBalanced(RestTemplate restTemplate);
@@ -180,6 +190,11 @@ public abstract class AbstractLoadBalancerAutoConfigurationTests {
return RestClient.builder();
}
+ @Bean
+ HttpClientServiceProperties httpClientServiceProperties() {
+ return new HttpClientServiceProperties();
+ }
+
}
@Configuration(proxyBeanMethods = false)
diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/LoadBalancerRestClientHttpServiceGroupConfigurerTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/LoadBalancerRestClientHttpServiceGroupConfigurerTests.java
new file mode 100644
index 00000000..affe5889
--- /dev/null
+++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/LoadBalancerRestClientHttpServiceGroupConfigurerTests.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2012-2025 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.client.loadbalancer;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Predicate;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.http.client.service.HttpClientServiceProperties;
+import org.springframework.boot.autoconfigure.http.client.service.HttpClientServiceProperties.Group;
+import org.springframework.web.client.RestClient;
+import org.springframework.web.service.invoker.HttpServiceProxyFactory;
+import org.springframework.web.service.registry.HttpServiceGroup;
+import org.springframework.web.service.registry.HttpServiceGroupConfigurer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link LoadBalancerRestClientHttpServiceGroupConfigurer}
+ *
+ * @author Olga Maciaszek-Sharma
+ */
+@SuppressWarnings({ "removal" })
+class LoadBalancerRestClientHttpServiceGroupConfigurerTests {
+
+ private static final String GROUP_NAME = "testService";
+
+ private HttpClientServiceProperties clientServiceProperties;
+
+ private ObjectProvider interceptorProvider;
+
+ @BeforeEach
+ void setup() {
+ DeferringLoadBalancerInterceptor interceptor = mock(DeferringLoadBalancerInterceptor.class);
+ interceptorProvider = new SimpleObjectProvider<>(interceptor);
+ clientServiceProperties = new HttpClientServiceProperties();
+ }
+
+ @Test
+ void shouldAddInterceptorWhenBaseUrlIsNotSet() {
+ LoadBalancerRestClientHttpServiceGroupConfigurer configurer = new LoadBalancerRestClientHttpServiceGroupConfigurer(
+ interceptorProvider, clientServiceProperties);
+ TestGroups groups = new TestGroups();
+
+ configurer.configureGroups(groups);
+
+ groups.builder.requestInterceptors(interceptors -> {
+ assertThat(interceptors).hasSize(1);
+ assertThat(interceptors.get(0).getClass()).isEqualTo(DeferringLoadBalancerInterceptor.class);
+ });
+ }
+
+ @Test
+ void shouldAddInterceptorWhenBaseUrlHasLbScheme() {
+ Group group = new Group();
+ group.setBaseUrl("lb://" + GROUP_NAME + "/path");
+ clientServiceProperties.getGroup().put(GROUP_NAME, group);
+ LoadBalancerRestClientHttpServiceGroupConfigurer configurer = new LoadBalancerRestClientHttpServiceGroupConfigurer(
+ interceptorProvider, clientServiceProperties);
+ TestGroups groups = new TestGroups();
+
+ configurer.configureGroups(groups);
+
+ groups.builder.requestInterceptors(interceptors -> {
+ assertThat(interceptors).hasSize(1);
+ assertThat(interceptors.get(0).getClass()).isEqualTo(DeferringLoadBalancerInterceptor.class);
+ });
+ }
+
+ @Test
+ void shouldNotAddInterceptorWhenBaseDoesNotHaveLbScheme() {
+ Group group = new Group();
+ group.setBaseUrl("http://" + GROUP_NAME + "/path");
+ clientServiceProperties.getGroup().put(GROUP_NAME, group);
+ LoadBalancerRestClientHttpServiceGroupConfigurer configurer = new LoadBalancerRestClientHttpServiceGroupConfigurer(
+ interceptorProvider, clientServiceProperties);
+ TestGroups groups = new TestGroups();
+
+ configurer.configureGroups(groups);
+
+ groups.builder.requestInterceptors(interceptors -> assertThat(interceptors).hasSize(0));
+ }
+
+ private static class TestGroups implements HttpServiceGroupConfigurer.Groups {
+
+ RestClient.Builder builder = RestClient.builder();
+
+ @Override
+ public HttpServiceGroupConfigurer.Groups filterByName(String... groupNames) {
+ throw new UnsupportedOperationException("Please, implement me.");
+ }
+
+ @Override
+ public HttpServiceGroupConfigurer.Groups filter(Predicate predicate) {
+ throw new UnsupportedOperationException("Please, implement me.");
+ }
+
+ @Override
+ public void forEachClient(HttpServiceGroupConfigurer.ClientCallback configurer) {
+
+ }
+
+ @Override
+ public void forEachProxyFactory(HttpServiceGroupConfigurer.ProxyFactoryCallback configurer) {
+
+ }
+
+ @Override
+ public void forEachGroup(HttpServiceGroupConfigurer.GroupCallback groupConfigurer) {
+ groupConfigurer.withGroup(
+ new TestGroup(GROUP_NAME, HttpServiceGroup.ClientType.REST_CLIENT, new HashSet<>()), builder,
+ HttpServiceProxyFactory.builder());
+ }
+
+ }
+
+ private record TestGroup(String name, ClientType clientType,
+ Set> httpServiceTypes) implements HttpServiceGroup {
+
+ }
+
+}
diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/LoadBalancerUriToolsTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/LoadBalancerUriToolsTests.java
index 9a70c6c1..be40f95a 100644
--- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/LoadBalancerUriToolsTests.java
+++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/LoadBalancerUriToolsTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2025 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.
diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerWebClientHttpServiceGroupConfigurerTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerWebClientHttpServiceGroupConfigurerTests.java
new file mode 100644
index 00000000..34e3e402
--- /dev/null
+++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/LoadBalancerWebClientHttpServiceGroupConfigurerTests.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2012-2025 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.client.loadbalancer.reactive;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.function.Predicate;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.http.client.reactive.service.ReactiveHttpClientServiceProperties;
+import org.springframework.cloud.client.loadbalancer.SimpleObjectProvider;
+import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.service.invoker.HttpServiceProxyFactory;
+import org.springframework.web.service.registry.HttpServiceGroup;
+import org.springframework.web.service.registry.HttpServiceGroupConfigurer;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link LoadBalancerWebClientHttpServiceGroupConfigurer}
+ *
+ * @author Olga Maciaszek-Sharma
+ */
+@SuppressWarnings({ "unchecked", "removal" })
+class LoadBalancerWebClientHttpServiceGroupConfigurerTests {
+
+ private static final String GROUP_NAME = "testService";
+
+ private ReactiveHttpClientServiceProperties clientServiceProperties;
+
+ private ObjectProvider> exchangeFilterFunctionProvider;
+
+ @BeforeEach
+ void setup() {
+ DeferringLoadBalancerExchangeFilterFunction exchangeFilterFunction = mock(
+ DeferringLoadBalancerExchangeFilterFunction.class);
+ exchangeFilterFunctionProvider = new SimpleObjectProvider<>(exchangeFilterFunction);
+ clientServiceProperties = new ReactiveHttpClientServiceProperties();
+ }
+
+ @Test
+ void shouldAddInterceptorWhenBaseUrlIsNotSet() {
+ LoadBalancerWebClientHttpServiceGroupConfigurer configurer = new LoadBalancerWebClientHttpServiceGroupConfigurer(
+ exchangeFilterFunctionProvider, clientServiceProperties);
+ TestGroups groups = new TestGroups();
+
+ configurer.configureGroups(groups);
+
+ groups.builder.filters(filterFunctions -> {
+ assertThat(filterFunctions).hasSize(1);
+ assertThat(filterFunctions.get(0).getClass()).isEqualTo(DeferringLoadBalancerExchangeFilterFunction.class);
+ });
+ }
+
+ @Test
+ void shouldAddInterceptorWhenBaseUrlHasLbScheme() {
+ ReactiveHttpClientServiceProperties.Group group = new ReactiveHttpClientServiceProperties.Group();
+ group.setBaseUrl("lb://" + GROUP_NAME + "/path");
+ clientServiceProperties.getGroup().put(GROUP_NAME, group);
+ LoadBalancerWebClientHttpServiceGroupConfigurer configurer = new LoadBalancerWebClientHttpServiceGroupConfigurer(
+ exchangeFilterFunctionProvider, clientServiceProperties);
+ TestGroups groups = new TestGroups();
+
+ configurer.configureGroups(groups);
+
+ groups.builder.filters(filterFunctions -> {
+ assertThat(filterFunctions).hasSize(1);
+ assertThat(filterFunctions.get(0).getClass()).isEqualTo(DeferringLoadBalancerExchangeFilterFunction.class);
+ });
+ }
+
+ @Test
+ void shouldNotAddInterceptorWhenBaseDoesNotHaveLbScheme() {
+ ReactiveHttpClientServiceProperties.Group group = new ReactiveHttpClientServiceProperties.Group();
+ group.setBaseUrl("https://" + GROUP_NAME + "/path");
+ clientServiceProperties.getGroup().put(GROUP_NAME, group);
+ LoadBalancerWebClientHttpServiceGroupConfigurer configurer = new LoadBalancerWebClientHttpServiceGroupConfigurer(
+ exchangeFilterFunctionProvider, clientServiceProperties);
+ TestGroups groups = new TestGroups();
+
+ configurer.configureGroups(groups);
+
+ groups.builder.filters(filterFunctions -> assertThat(filterFunctions).hasSize(0));
+ }
+
+ private static class TestGroups implements HttpServiceGroupConfigurer.Groups {
+
+ WebClient.Builder builder = WebClient.builder();
+
+ //
+ @Override
+ public HttpServiceGroupConfigurer.Groups filterByName(String... groupNames) {
+ throw new UnsupportedOperationException("Please, implement me.");
+ }
+
+ @Override
+ public HttpServiceGroupConfigurer.Groups filter(Predicate predicate) {
+ throw new UnsupportedOperationException("Please, implement me.");
+ }
+
+ @Override
+ public void forEachClient(HttpServiceGroupConfigurer.ClientCallback configurer) {
+
+ }
+
+ @Override
+ public void forEachProxyFactory(HttpServiceGroupConfigurer.ProxyFactoryCallback configurer) {
+
+ }
+
+ @Override
+ public void forEachGroup(HttpServiceGroupConfigurer.GroupCallback groupConfigurer) {
+ groupConfigurer.withGroup(
+ new TestGroup(GROUP_NAME, HttpServiceGroup.ClientType.WEB_CLIENT, new HashSet<>()), builder,
+ HttpServiceProxyFactory.builder());
+ }
+
+ }
+
+ private record TestGroup(String name, ClientType clientType,
+ Set> httpServiceTypes) implements HttpServiceGroup {
+
+ }
+
+}
diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerClientAutoConfigurationTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerClientAutoConfigurationTests.java
index 0d0f3c6c..1b5e8686 100644
--- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerClientAutoConfigurationTests.java
+++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/ReactorLoadBalancerClientAutoConfigurationTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2025 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.
@@ -23,19 +23,24 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.WebApplicationType;
+import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.http.client.reactive.service.ReactiveHttpClientServiceProperties;
import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
+import org.springframework.cloud.client.loadbalancer.LoadBalancerRestClientHttpServiceGroupConfigurer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.web.reactive.function.client.WebClient;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.BDDAssertions.then;
import static org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerTestUtils.assertLoadBalanced;
import static org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerTestUtils.getFilters;
@@ -150,6 +155,18 @@ public class ReactorLoadBalancerClientAutoConfigurationTests {
then(clientProperties.getHealthCheck().getInterval()).isEqualTo(Duration.ofSeconds(30));
}
+ @Test
+ void loadBalancerRestClientHttpServiceGroupConfigurerPresent() {
+ new ApplicationContextRunner()
+ .withConfiguration(AutoConfigurations.of(ReactorLoadBalancerClientAutoConfiguration.class,
+ LoadBalancerBeanPostProcessorAutoConfiguration.class))
+ .withUserConfiguration(OneWebClientBuilder.class)
+ .run(context -> {
+ assertThat(context.getBeansOfType(LoadBalancerWebClientHttpServiceGroupConfigurer.class)).hasSize(1);
+ assertThat(context.getBeansOfType(LoadBalancerRestClientHttpServiceGroupConfigurer.class)).hasSize(0);
+ });
+ }
+
private ConfigurableApplicationContext init(Class> config) {
return LoadBalancerTestUtils.init(config, ReactorLoadBalancerClientAutoConfiguration.class,
LoadBalancerBeanPostProcessorAutoConfiguration.class);
@@ -211,6 +228,11 @@ public class ReactorLoadBalancerClientAutoConfigurationTests {
return new TestService(loadBalancedWebClientBuilder());
}
+ @Bean
+ ReactiveHttpClientServiceProperties reactiveHttpClientServiceProperties() {
+ return new ReactiveHttpClientServiceProperties();
+ }
+
private static final class TestService {
public final WebClient webClient;
diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionTests.java
index 29bef5bc..5c84e5c4 100644
--- a/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionTests.java
+++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/client/loadbalancer/reactive/RetryableLoadBalancerExchangeFilterFunctionTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2025 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.
@@ -31,6 +31,7 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
+import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFunction;
@@ -75,7 +76,7 @@ class RetryableLoadBalancerExchangeFilterFunctionTests {
when(factory.getInstance("test")).thenReturn(new TestReactiveLoadBalancer());
when(factory.getProperties(any())).thenReturn(properties);
when(clientRequest.headers()).thenReturn(new HttpHeaders());
- when(clientRequest.cookies()).thenReturn(new HttpHeaders());
+ when(clientRequest.cookies()).thenReturn(new LinkedMultiValueMap<>());
}
diff --git a/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/SpringBootDependencyTests.java b/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/SpringBootDependencyTests.java
index 825ddc6a..0bc8910a 100644
--- a/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/SpringBootDependencyTests.java
+++ b/spring-cloud-commons/src/test/java/org/springframework/cloud/configuration/SpringBootDependencyTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2020 the original author or authors.
+ * Copyright 2012-2025 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.
@@ -27,6 +27,7 @@ import static org.assertj.core.api.BDDAssertions.then;
/**
* @author Marcin Grzejszczak
+ * @author Olga Maciaszek-Sharma
*/
public class SpringBootDependencyTests {
@@ -176,8 +177,8 @@ public class SpringBootDependencyTests {
@Test
public void should_match_against_current_manifest() {
try {
- verifyCurrentVersionFromManifest("3.5");
- verifyCurrentVersionFromManifest("3.5.x");
+ verifyCurrentVersionFromManifest("4.0");
+ verifyCurrentVersionFromManifest("4.0.x");
}
catch (AssertionError e) {
// if (e.getMessage() != null && e.getMessage().contains("3.3.")) {
@@ -204,7 +205,7 @@ public class SpringBootDependencyTests {
@Test
public void should_match_against_current_predicate() {
- List acceptedVersions = Collections.singletonList("3.0");
+ List acceptedVersions = Collections.singletonList("4.0");
SpringBootVersionVerifier versionVerifier = new SpringBootVersionVerifier(acceptedVersions) {
@Override
String getVersionFromManifest() {
@@ -212,7 +213,7 @@ public class SpringBootDependencyTests {
}
};
versionVerifier.ACCEPTED_VERSIONS.clear();
- versionVerifier.ACCEPTED_VERSIONS.put("3.0", versionVerifier.is3_5());
+ versionVerifier.ACCEPTED_VERSIONS.put("4.0", versionVerifier.is4_0());
VerificationResult verificationResult = versionVerifier.verify();
@@ -222,7 +223,7 @@ public class SpringBootDependencyTests {
@Test
public void should_match_against_current_predicate_with_version_ending_with_x() {
- List acceptedVersions = Collections.singletonList("3.0.x");
+ List acceptedVersions = Collections.singletonList("4.0.x");
SpringBootVersionVerifier versionVerifier = new SpringBootVersionVerifier(acceptedVersions) {
@Override
String getVersionFromManifest() {
@@ -230,7 +231,7 @@ public class SpringBootDependencyTests {
}
};
versionVerifier.ACCEPTED_VERSIONS.clear();
- versionVerifier.ACCEPTED_VERSIONS.put("3.0", versionVerifier.is3_5());
+ versionVerifier.ACCEPTED_VERSIONS.put("4.0", versionVerifier.is4_0());
VerificationResult verificationResult = versionVerifier.verify();
diff --git a/spring-cloud-context-integration-tests/pom.xml b/spring-cloud-context-integration-tests/pom.xml
index 25a42ea1..80b02ae3 100644
--- a/spring-cloud-context-integration-tests/pom.xml
+++ b/spring-cloud-context-integration-tests/pom.xml
@@ -7,7 +7,7 @@
org.springframework.cloudspring-cloud-commons-parent
- 4.3.1-SNAPSHOT
+ 5.0.0-SNAPSHOT..spring-cloud-context-integration-tests
diff --git a/spring-cloud-context-webflux-integration-tests/pom.xml b/spring-cloud-context-webflux-integration-tests/pom.xml
index 5641eb56..e5ab3be9 100644
--- a/spring-cloud-context-webflux-integration-tests/pom.xml
+++ b/spring-cloud-context-webflux-integration-tests/pom.xml
@@ -7,7 +7,7 @@
org.springframework.cloudspring-cloud-commons-parent
- 4.3.1-SNAPSHOT
+ 5.0.0-SNAPSHOT..spring-cloud-context-webflux-integration-tests
diff --git a/spring-cloud-context/pom.xml b/spring-cloud-context/pom.xml
index a945c9a3..2fd63822 100644
--- a/spring-cloud-context/pom.xml
+++ b/spring-cloud-context/pom.xml
@@ -7,7 +7,7 @@
org.springframework.cloudspring-cloud-commons-parent
- 4.3.1-SNAPSHOT
+ 5.0.0-SNAPSHOT..spring-cloud-context
diff --git a/spring-cloud-loadbalancer/pom.xml b/spring-cloud-loadbalancer/pom.xml
index 185b5130..7a093345 100644
--- a/spring-cloud-loadbalancer/pom.xml
+++ b/spring-cloud-loadbalancer/pom.xml
@@ -6,7 +6,7 @@
org.springframework.cloudspring-cloud-commons-parent
- 4.3.1-SNAPSHOT
+ 5.0.0-SNAPSHOT..spring-cloud-loadbalancer
diff --git a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilder.java b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilder.java
index 19a3c72c..efe1cdab 100644
--- a/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilder.java
+++ b/spring-cloud-loadbalancer/src/main/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSupplierBuilder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013-2023 the original author or authors.
+ * Copyright 2013-2025 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.
@@ -380,9 +380,10 @@ public final class ServiceInstanceListSupplierBuilder {
return new HealthCheckServiceInstanceListSupplier(delegate, loadBalancerClientFactory,
(serviceInstance, healthCheckPath) -> webClient.get()
.uri(UriComponentsBuilder.fromUriString(getUri(serviceInstance, healthCheckPath)).build().toUri())
- .exchange()
- .flatMap(clientResponse -> clientResponse.releaseBody()
- .thenReturn(HttpStatus.OK.equals(clientResponse.statusCode()))));
+ .retrieve()
+ .toBodilessEntity()
+ .map(response -> HttpStatus.OK.equals(response.getStatusCode()))
+ .onErrorReturn(false));
}
private ServiceInstanceListSupplier blockingHealthCheckServiceInstanceListSupplier(RestTemplate restTemplate,
diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/blocking/XForwardedHeadersTransformerTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/blocking/XForwardedHeadersTransformerTests.java
index 6899772b..183cd7d0 100644
--- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/blocking/XForwardedHeadersTransformerTests.java
+++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/blocking/XForwardedHeadersTransformerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2021 the original author or authors.
+ * Copyright 2012-2025 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.
@@ -65,9 +65,9 @@ class XForwardedHeadersTransformerTests {
HttpRequest newRequest = transformer.transformRequest(request, serviceInstance);
- assertThat(newRequest.getHeaders()).containsKey("X-Forwarded-Host");
+ assertThat(newRequest.getHeaders().containsHeader("X-Forwarded-Host")).isTrue();
assertThat(newRequest.getHeaders().getFirst("X-Forwarded-Host")).isEqualTo("google.com");
- assertThat(newRequest.getHeaders()).containsKey("X-Forwarded-Proto");
+ assertThat(newRequest.getHeaders().containsHeader("X-Forwarded-Proto")).isTrue();
assertThat(newRequest.getHeaders().getFirst("X-Forwarded-Proto")).isEqualTo("https");
}
@@ -78,8 +78,8 @@ class XForwardedHeadersTransformerTests {
XForwardedHeadersTransformer transformer = new XForwardedHeadersTransformer(loadBalancerClientFactory);
HttpRequest newRequest = transformer.transformRequest(request, serviceInstance);
- assertThat(newRequest.getHeaders()).doesNotContainKey("X-Forwarded-Host");
- assertThat(newRequest.getHeaders()).doesNotContainKey("X-Forwarded-Proto");
+ assertThat(newRequest.getHeaders().containsHeader("X-Forwarded-Host")).isFalse();
+ assertThat(newRequest.getHeaders().containsHeader("X-Forwarded-Proto")).isFalse();
}
}
diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/HealthCheckServiceInstanceListSupplierTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/HealthCheckServiceInstanceListSupplierTests.java
index 1507fbf1..f5889529 100644
--- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/HealthCheckServiceInstanceListSupplierTests.java
+++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/HealthCheckServiceInstanceListSupplierTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2023 the original author or authors.
+ * Copyright 2012-2025 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.
@@ -703,7 +703,7 @@ class HealthCheckServiceInstanceListSupplierTests {
}
@Test
- void shouldCheckUseProvidedPortForHealthCheckRequest() {
+ void shouldCheckUserProvidedPortForHealthCheckRequest() {
Throwable exception = catchThrowable(() -> {
String serviceId = "ignored-service";
properties.getHealthCheck().setPort(8888);
@@ -718,7 +718,7 @@ class HealthCheckServiceInstanceListSupplierTests {
listSupplier.isAlive(serviceInstance).block();
});
- assertThat(exception).hasMessageContaining("Connection refused: /127.0.0.1:888");
+ assertThat(exception).hasMessageContaining("Connection refused: /127.0.0.1:8888");
}
private static Stream healthCheckFunctions() {
diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformerTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformerTests.java
index a7d8a376..a10f922d 100644
--- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformerTests.java
+++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/LoadBalancerServiceInstanceCookieTransformerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2021 the original author or authors.
+ * Copyright 2012-2025 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.
@@ -74,7 +74,7 @@ class LoadBalancerServiceInstanceCookieTransformerTests {
void shouldReturnPassedRequestWhenNoServiceInstance() {
HttpRequest newRequest = transformer.transformRequest(request, null);
- assertThat(newRequest.getHeaders()).doesNotContainKey(HttpHeaders.COOKIE);
+ assertThat(newRequest.getHeaders().containsHeader(HttpHeaders.COOKIE)).isFalse();
}
@Test
@@ -82,7 +82,7 @@ class LoadBalancerServiceInstanceCookieTransformerTests {
properties.getStickySession().setInstanceIdCookieName(null);
HttpRequest newRequest = transformer.transformRequest(request, serviceInstance);
- assertThat(newRequest.getHeaders()).doesNotContainKey(HttpHeaders.COOKIE);
+ assertThat(newRequest.getHeaders().containsHeader(HttpHeaders.COOKIE)).isFalse();
}
@Test
@@ -90,7 +90,7 @@ class LoadBalancerServiceInstanceCookieTransformerTests {
properties.getStickySession().setInstanceIdCookieName("");
HttpRequest newRequest = transformer.transformRequest(request, serviceInstance);
- assertThat(newRequest.getHeaders()).doesNotContainKey(HttpHeaders.COOKIE);
+ assertThat(newRequest.getHeaders().containsHeader(HttpHeaders.COOKIE)).isFalse();
}
diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/RequestBasedStickySessionServiceInstanceListSupplierTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/RequestBasedStickySessionServiceInstanceListSupplierTests.java
index ad2aee6e..21500ca7 100644
--- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/RequestBasedStickySessionServiceInstanceListSupplierTests.java
+++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/RequestBasedStickySessionServiceInstanceListSupplierTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2021 the original author or authors.
+ * Copyright 2012-2025 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.
@@ -17,6 +17,7 @@
package org.springframework.cloud.loadbalancer.core;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
@@ -31,7 +32,8 @@ import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.RequestData;
import org.springframework.cloud.client.loadbalancer.RequestDataContext;
-import org.springframework.http.HttpHeaders;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.client.ClientRequest;
import static org.assertj.core.api.Assertions.assertThat;
@@ -72,8 +74,8 @@ class RequestBasedStickySessionServiceInstanceListSupplierTests {
@Test
void shouldReturnInstanceBasedOnCookieFromClientRequest() {
- HttpHeaders headers = new HttpHeaders();
- headers.add(properties.getStickySession().getInstanceIdCookieName(), "test-1");
+ MultiValueMap headers = new LinkedMultiValueMap<>();
+ headers.put(properties.getStickySession().getInstanceIdCookieName(), Collections.singletonList("test-1"));
when(clientRequest.cookies()).thenReturn(headers);
Request request = new DefaultRequest<>(
new RequestDataContext(new RequestData(clientRequest)));
@@ -86,8 +88,8 @@ class RequestBasedStickySessionServiceInstanceListSupplierTests {
@Test
void shouldReturnAllDelegateInstancesIfInstanceBasedOnCookieFromClientRequestNotFound() {
- HttpHeaders headers = new HttpHeaders();
- headers.add(properties.getStickySession().getInstanceIdCookieName(), "test-4");
+ MultiValueMap headers = new LinkedMultiValueMap<>();
+ headers.put(properties.getStickySession().getInstanceIdCookieName(), Collections.singletonList("test-4"));
when(clientRequest.cookies()).thenReturn(headers);
Request request = new DefaultRequest<>(
new RequestDataContext(new RequestData(clientRequest)));
@@ -99,7 +101,7 @@ class RequestBasedStickySessionServiceInstanceListSupplierTests {
@Test
void shouldReturnAllInstancesFromDelegateIfClientRequestHasNoCookie() {
- when(clientRequest.cookies()).thenReturn(new HttpHeaders());
+ when(clientRequest.cookies()).thenReturn(new LinkedMultiValueMap<>());
Request request = new DefaultRequest<>(
new RequestDataContext(new RequestData(clientRequest)));
diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSuppliersTestUtils.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSuppliersTestUtils.java
index 8dceeee7..068ab9f1 100644
--- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSuppliersTestUtils.java
+++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/ServiceInstanceListSuppliersTestUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2013-2023 the original author or authors.
+ * Copyright 2013-2025 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.
@@ -26,6 +26,7 @@ import org.springframework.http.HttpStatus;
import org.springframework.web.client.RestClient;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
+import org.springframework.web.reactive.function.client.WebClientRequestException;
import org.springframework.web.util.UriComponentsBuilder;
import static org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplierBuilder.getUri;
@@ -46,9 +47,10 @@ final class ServiceInstanceListSuppliersTestUtils {
static BiFunction> healthCheckFunction(WebClient webClient) {
return (serviceInstance, healthCheckPath) -> webClient.get()
.uri(UriComponentsBuilder.fromUriString(getUri(serviceInstance, healthCheckPath)).build().toUri())
- .exchange()
- .flatMap(clientResponse -> clientResponse.releaseBody()
- .thenReturn(HttpStatus.OK.equals(clientResponse.statusCode())));
+ .retrieve()
+ .toBodilessEntity()
+ .map(response -> HttpStatus.OK.equals(response.getStatusCode()))
+ .onErrorReturn(throwable -> !(throwable instanceof WebClientRequestException), false);
}
static BiFunction> healthCheckFunction(RestTemplate restTemplate) {
diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/XForwardedHeadersTransformerTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/XForwardedHeadersTransformerTests.java
index 3c6b500c..948e44f0 100644
--- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/XForwardedHeadersTransformerTests.java
+++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/core/XForwardedHeadersTransformerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2012-2021 the original author or authors.
+ * Copyright 2012-2025 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.
@@ -66,9 +66,9 @@ class XForwardedHeadersTransformerTests {
ClientRequest newRequest = transformer.transformRequest(request, serviceInstance);
- assertThat(newRequest.headers()).containsKey("X-Forwarded-Host");
+ assertThat(newRequest.headers().containsHeader("X-Forwarded-Host")).isTrue();
assertThat(newRequest.headers().getFirst("X-Forwarded-Host")).isEqualTo("spring.io");
- assertThat(newRequest.headers()).containsKey("X-Forwarded-Proto");
+ assertThat(newRequest.headers().containsHeader("X-Forwarded-Proto")).isTrue();
assertThat(newRequest.headers().getFirst("X-Forwarded-Proto")).isEqualTo("https");
}
@@ -79,8 +79,8 @@ class XForwardedHeadersTransformerTests {
ClientRequest newRequest = transformer.transformRequest(request, serviceInstance);
- assertThat(newRequest.headers()).doesNotContainKey("X-Forwarded-Host");
- assertThat(newRequest.headers()).doesNotContainKey("X-Forwarded-Proto");
+ assertThat(newRequest.headers().containsHeader("X-Forwarded-Host")).isFalse();
+ assertThat(newRequest.headers().containsHeader("X-Forwarded-Proto")).isFalse();
}
}
diff --git a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/stats/MicrometerStatsLoadBalancerLifecycleTests.java b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/stats/MicrometerStatsLoadBalancerLifecycleTests.java
index c5d7bc5c..e6b14386 100644
--- a/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/stats/MicrometerStatsLoadBalancerLifecycleTests.java
+++ b/spring-cloud-loadbalancer/src/test/java/org/springframework/cloud/loadbalancer/stats/MicrometerStatsLoadBalancerLifecycleTests.java
@@ -44,6 +44,7 @@ import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalanc
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
+import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMapAdapter;
import static org.assertj.core.api.Assertions.assertThat;
@@ -71,7 +72,7 @@ class MicrometerStatsLoadBalancerLifecycleTests {
@Test
void shouldRecordSuccessfulTimedRequest() {
RequestData requestData = new RequestData(HttpMethod.GET, URI.create("http://test.org/test"), new HttpHeaders(),
- new HttpHeaders(), new HashMap<>());
+ new LinkedMultiValueMap<>(), new HashMap<>());
Request