From 1125c67f29e1d4694d88c6d0bd81323cf394d651 Mon Sep 17 00:00:00 2001 From: 98001yash Date: Thu, 2 Apr 2026 16:18:49 +0530 Subject: [PATCH 1/4] Improve documentation for Load Balancing with Eureka and Spring Cloud LoadBalancer Signed-off-by: 98001yash --- .../ROOT/pages/spring-cloud-netflix.adoc | 133 +++++++++++++++++- 1 file changed, 126 insertions(+), 7 deletions(-) diff --git a/docs/modules/ROOT/pages/spring-cloud-netflix.adoc b/docs/modules/ROOT/pages/spring-cloud-netflix.adoc index 243db5b0cb..5d36820ca8 100755 --- a/docs/modules/ROOT/pages/spring-cloud-netflix.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-netflix.adoc @@ -389,15 +389,134 @@ the ability to refresh Eureka clients. To do this set `eureka.client.refresh.en === Using Eureka with Spring Cloud LoadBalancer -We offer support for the Spring Cloud LoadBalancer `ZonePreferenceServiceInstanceListSupplier`. -The `zone` value from the Eureka instance metadata (`eureka.instance.metadataMap.zone`) is used for setting the -value of `spring-cloud-loadbalancer-zone` property that is used to filter service instances by zone. +Spring Cloud Netflix integrates with Spring Cloud LoadBalancer to provide +client-side load balancing for services registered with Eureka. -If that is missing and if the `spring.cloud.loadbalancer.eureka.approximateZoneFromHostname` flag is set to `true`, -it can use the domain name from the server hostname as a proxy for the zone. +When a client makes a request to another service using a logical service ID, +Spring Cloud LoadBalancer selects an appropriate service instance based on +available instances retrieved from Eureka. -If there is no other source of zone data, then a guess is made, based on the client configuration (as opposed to the instance configuration). -We take `eureka.client.availabilityZones`, which is a map from region name to a list of zones, and pull out the first zone for the instance's own region (that is, the `eureka.client.region`, which defaults to "us-east-1", for compatibility with native Netflix). +==== How It Works + +The load balancing process follows these steps: + +1. A client makes a request using a service ID (for example, `http://user-service`). +2. The `DiscoveryClient` retrieves all available instances of the service from Eureka. +3. Spring Cloud LoadBalancer selects one instance using a load balancing strategy. +4. The request is routed to the selected service instance. + +This mechanism enables service discovery and client-side load balancing across multiple instances. + +==== Configuration Example + +The following example shows a minimal configuration for using Eureka with Spring Cloud LoadBalancer: + +.application.yml +[source,yaml] +---- +spring: + application: + name: user-service + cloud: + loadbalancer: + ribbon: + enabled: false + +eureka: + client: + serviceUrl: + defaultZone: http://localhost:8761/eureka/ +---- + +NOTE: Ribbon is deprecated and replaced by Spring Cloud LoadBalancer. + +==== Using Load-Balanced Clients + +Spring Cloud LoadBalancer can be used with `RestTemplate` or `WebClient`. + +.Example using RestTemplate +[source,java,indent=0] +---- +@Bean +@LoadBalanced +public RestTemplate restTemplate() { + return new RestTemplate(); +} +---- + +.Example using WebClient +[source,java,indent=0] +---- +@Bean +@LoadBalanced +public WebClient.Builder webClientBuilder() { + return WebClient.builder(); +} +---- + +With these configurations, requests made using a service name are automatically resolved +to an actual service instance registered in Eureka. + +==== Zone-Based Load Balancing + +Spring Cloud LoadBalancer supports zone-based routing by using metadata provided +by Eureka instances. + +To define a zone for a service instance, use the following configuration: + +.application.yml +[source,yaml] +---- +eureka: + instance: + metadataMap: + zone: zone1 +---- + +When multiple instances exist across zones, the load balancer can prioritize instances +in the same zone as the client. + +The `ZonePreferenceServiceInstanceListSupplier` is used to filter instances based on zone. + +==== Zone Resolution + +The zone used by the load balancer can be determined in multiple ways: + +- Explicitly via `eureka.instance.metadataMap.zone` +- From the hostname if the following property is enabled: + +[source] +---- +spring.cloud.loadbalancer.eureka.approximateZoneFromHostname=true +---- + +When this property is enabled, the system attempts to infer the zone from the domain name +of the service instance hostname. + +If no zone information is available, the system falls back to the client configuration, +such as `eureka.client.availabilityZones`. + +==== Troubleshooting + +If load balancing does not behave as expected, consider the following: + +- Ensure that all service instances are properly registered in Eureka +- Verify that `metadataMap.zone` is correctly configured +- Check that the Eureka client is able to fetch registry data +- Enable debug logs to inspect load balancer decisions + +==== Best Practices + +- Always define zone metadata for service instances in multi-zone deployments +- Use consistent naming conventions for zones +- Avoid relying solely on hostname-based zone approximation in production +- Monitor service instance health and registration status + +==== Summary + +Spring Cloud LoadBalancer, when combined with Eureka, provides a flexible and dynamic +approach to client-side load balancing. It enables services to discover and communicate +with each other efficiently while distributing traffic across available instances. === AOT and Native Image Support From 1d026163899a04452dfbb73b14af9302933afb47 Mon Sep 17 00:00:00 2001 From: 98001yash Date: Thu, 2 Apr 2026 17:54:08 +0530 Subject: [PATCH 2/4] Add support for custom actuator health status mapping in EurekaHealthCheckHandler Signed-off-by: 98001yash --- .../EurekaClientStatusMappingProperties.java | 39 +++++++++++++++++++ .../eureka/EurekaHealthCheckHandler.java | 28 ++++++++++++- .../eureka/EurekaHealthCheckHandlerTests.java | 26 +++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientStatusMappingProperties.java diff --git a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientStatusMappingProperties.java b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientStatusMappingProperties.java new file mode 100644 index 0000000000..d7606d48cd --- /dev/null +++ b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientStatusMappingProperties.java @@ -0,0 +1,39 @@ +/* + * Copyright 2013-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.netflix.eureka; + +import java.util.HashMap; +import java.util.Map; + +import com.netflix.appinfo.InstanceInfo.InstanceStatus; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties("eureka.client.status.mapping") +public class EurekaClientStatusMappingProperties { + + private Map mapping = new HashMap<>(); + + public Map getMapping() { + return mapping; + } + + public void setMapping(Map mapping) { + this.mapping = mapping; + } + +} diff --git a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaHealthCheckHandler.java b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaHealthCheckHandler.java index ff89ef7f94..fbb2106abf 100644 --- a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaHealthCheckHandler.java +++ b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaHealthCheckHandler.java @@ -18,6 +18,7 @@ import java.util.HashMap; import java.util.HashSet; +import java.util.Locale; import java.util.Map; import java.util.Set; @@ -83,6 +84,8 @@ public class EurekaHealthCheckHandler private final Map healthContributors = new HashMap<>(); + private final EurekaClientStatusMappingProperties properties; + /** * {@code true} until the context is stopped. */ @@ -91,9 +94,14 @@ public class EurekaHealthCheckHandler private final Map reactiveHealthContributors = new HashMap<>(); public EurekaHealthCheckHandler(StatusAggregator statusAggregator) { + this(statusAggregator, new EurekaClientStatusMappingProperties()); + } + + public EurekaHealthCheckHandler(StatusAggregator statusAggregator, EurekaClientStatusMappingProperties properties) { + this.statusAggregator = statusAggregator; + this.properties = properties; Assert.notNull(statusAggregator, "StatusAggregator must not be null"); - } @Override @@ -179,9 +187,27 @@ else if (contributor instanceof ReactiveHealthIndicator) { } protected InstanceStatus mapToInstanceStatus(Status status) { + + if (status == null) { + return InstanceStatus.UNKNOWN; + } + + String statusCode = status.getCode().toLowerCase(Locale.ROOT); + + // 🔥 Custom mapping (case-insensitive) + if (properties != null && properties.getMapping() != null) { + for (Map.Entry entry : properties.getMapping().entrySet()) { + if (entry.getKey().equalsIgnoreCase(statusCode)) { + return entry.getValue(); + } + } + } + + // 🔁 Default mapping if (!STATUS_MAPPING.containsKey(status)) { return InstanceStatus.UNKNOWN; } + return STATUS_MAPPING.get(status); } diff --git a/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaHealthCheckHandlerTests.java b/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaHealthCheckHandlerTests.java index f4eacdee14..4a64b14e06 100644 --- a/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaHealthCheckHandlerTests.java +++ b/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/EurekaHealthCheckHandlerTests.java @@ -35,6 +35,7 @@ import org.springframework.boot.health.contributor.HealthContributors; import org.springframework.boot.health.contributor.HealthIndicator; import org.springframework.boot.health.contributor.ReactiveHealthIndicator; +import org.springframework.boot.health.contributor.Status; import org.springframework.cloud.client.discovery.health.DiscoveryClientHealthIndicator; import org.springframework.cloud.client.discovery.health.DiscoveryCompositeHealthContributor; import org.springframework.cloud.client.discovery.health.DiscoveryHealthIndicator; @@ -161,6 +162,31 @@ void testCompositeComponentsOneDown() { assertThat(status).isEqualTo(InstanceStatus.DOWN); } + @Test + void testCustomStatusMapping() { + + EurekaClientStatusMappingProperties props = new EurekaClientStatusMappingProperties(); + props.getMapping().put("fatal", InstanceStatus.OUT_OF_SERVICE); + + EurekaHealthCheckHandler handler = new EurekaHealthCheckHandler(new SimpleStatusAggregator(), props); + + Status status = new Status("fatal"); + + InstanceStatus result = handler.mapToInstanceStatus(status); + + assertThat(result).isEqualTo(InstanceStatus.OUT_OF_SERVICE); + } + + @Test + void testNullStatusReturnsUnknown() { + + EurekaHealthCheckHandler handler = new EurekaHealthCheckHandler(new SimpleStatusAggregator()); + + InstanceStatus result = handler.mapToInstanceStatus(null); + + assertThat(result).isEqualTo(InstanceStatus.UNKNOWN); + } + private void initialize(Class... configurations) { ApplicationContext applicationContext = new AnnotationConfigApplicationContext(configurations); healthCheckHandler.setApplicationContext(applicationContext); From 40b6974882264131a27d5bbf4293f62b832f4f3b Mon Sep 17 00:00:00 2001 From: 98001yash Date: Fri, 3 Apr 2026 07:55:41 +0530 Subject: [PATCH 3/4] Revert unrelated documentation changes Signed-off-by: 98001yash --- .../ROOT/pages/spring-cloud-netflix.adoc | 133 +----------------- 1 file changed, 7 insertions(+), 126 deletions(-) diff --git a/docs/modules/ROOT/pages/spring-cloud-netflix.adoc b/docs/modules/ROOT/pages/spring-cloud-netflix.adoc index 5d36820ca8..243db5b0cb 100755 --- a/docs/modules/ROOT/pages/spring-cloud-netflix.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-netflix.adoc @@ -389,134 +389,15 @@ the ability to refresh Eureka clients. To do this set `eureka.client.refresh.en === Using Eureka with Spring Cloud LoadBalancer -Spring Cloud Netflix integrates with Spring Cloud LoadBalancer to provide -client-side load balancing for services registered with Eureka. +We offer support for the Spring Cloud LoadBalancer `ZonePreferenceServiceInstanceListSupplier`. +The `zone` value from the Eureka instance metadata (`eureka.instance.metadataMap.zone`) is used for setting the +value of `spring-cloud-loadbalancer-zone` property that is used to filter service instances by zone. -When a client makes a request to another service using a logical service ID, -Spring Cloud LoadBalancer selects an appropriate service instance based on -available instances retrieved from Eureka. +If that is missing and if the `spring.cloud.loadbalancer.eureka.approximateZoneFromHostname` flag is set to `true`, +it can use the domain name from the server hostname as a proxy for the zone. -==== How It Works - -The load balancing process follows these steps: - -1. A client makes a request using a service ID (for example, `http://user-service`). -2. The `DiscoveryClient` retrieves all available instances of the service from Eureka. -3. Spring Cloud LoadBalancer selects one instance using a load balancing strategy. -4. The request is routed to the selected service instance. - -This mechanism enables service discovery and client-side load balancing across multiple instances. - -==== Configuration Example - -The following example shows a minimal configuration for using Eureka with Spring Cloud LoadBalancer: - -.application.yml -[source,yaml] ----- -spring: - application: - name: user-service - cloud: - loadbalancer: - ribbon: - enabled: false - -eureka: - client: - serviceUrl: - defaultZone: http://localhost:8761/eureka/ ----- - -NOTE: Ribbon is deprecated and replaced by Spring Cloud LoadBalancer. - -==== Using Load-Balanced Clients - -Spring Cloud LoadBalancer can be used with `RestTemplate` or `WebClient`. - -.Example using RestTemplate -[source,java,indent=0] ----- -@Bean -@LoadBalanced -public RestTemplate restTemplate() { - return new RestTemplate(); -} ----- - -.Example using WebClient -[source,java,indent=0] ----- -@Bean -@LoadBalanced -public WebClient.Builder webClientBuilder() { - return WebClient.builder(); -} ----- - -With these configurations, requests made using a service name are automatically resolved -to an actual service instance registered in Eureka. - -==== Zone-Based Load Balancing - -Spring Cloud LoadBalancer supports zone-based routing by using metadata provided -by Eureka instances. - -To define a zone for a service instance, use the following configuration: - -.application.yml -[source,yaml] ----- -eureka: - instance: - metadataMap: - zone: zone1 ----- - -When multiple instances exist across zones, the load balancer can prioritize instances -in the same zone as the client. - -The `ZonePreferenceServiceInstanceListSupplier` is used to filter instances based on zone. - -==== Zone Resolution - -The zone used by the load balancer can be determined in multiple ways: - -- Explicitly via `eureka.instance.metadataMap.zone` -- From the hostname if the following property is enabled: - -[source] ----- -spring.cloud.loadbalancer.eureka.approximateZoneFromHostname=true ----- - -When this property is enabled, the system attempts to infer the zone from the domain name -of the service instance hostname. - -If no zone information is available, the system falls back to the client configuration, -such as `eureka.client.availabilityZones`. - -==== Troubleshooting - -If load balancing does not behave as expected, consider the following: - -- Ensure that all service instances are properly registered in Eureka -- Verify that `metadataMap.zone` is correctly configured -- Check that the Eureka client is able to fetch registry data -- Enable debug logs to inspect load balancer decisions - -==== Best Practices - -- Always define zone metadata for service instances in multi-zone deployments -- Use consistent naming conventions for zones -- Avoid relying solely on hostname-based zone approximation in production -- Monitor service instance health and registration status - -==== Summary - -Spring Cloud LoadBalancer, when combined with Eureka, provides a flexible and dynamic -approach to client-side load balancing. It enables services to discover and communicate -with each other efficiently while distributing traffic across available instances. +If there is no other source of zone data, then a guess is made, based on the client configuration (as opposed to the instance configuration). +We take `eureka.client.availabilityZones`, which is a map from region name to a list of zones, and pull out the first zone for the instance's own region (that is, the `eureka.client.region`, which defaults to "us-east-1", for compatibility with native Netflix). === AOT and Native Image Support From 4f0876946b470fad50c1eda4e26f59bb720124f0 Mon Sep 17 00:00:00 2001 From: 98001yash Date: Fri, 3 Apr 2026 08:16:24 +0530 Subject: [PATCH 4/4] Improve LoadBalancer documentation with Eureka integration Signed-off-by: 98001yash --- .../ROOT/pages/spring-cloud-netflix.adoc | 68 ++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/spring-cloud-netflix.adoc b/docs/modules/ROOT/pages/spring-cloud-netflix.adoc index 243db5b0cb..e2cc0b181b 100755 --- a/docs/modules/ROOT/pages/spring-cloud-netflix.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-netflix.adoc @@ -399,6 +399,73 @@ it can use the domain name from the server hostname as a proxy for the zone. If there is no other source of zone data, then a guess is made, based on the client configuration (as opposed to the instance configuration). We take `eureka.client.availabilityZones`, which is a map from region name to a list of zones, and pull out the first zone for the instance's own region (that is, the `eureka.client.region`, which defaults to "us-east-1", for compatibility with native Netflix). +The following sections provide additional details and practical guidance on using Spring Cloud LoadBalancer with Eureka. + +==== How Load Balancing Works + +Spring Cloud LoadBalancer works together with Eureka to provide client-side load balancing. + +When a client makes a request using a service ID (for example, `http://STORES/api`), the following steps occur: + +---- +Client → DiscoveryClient → LoadBalancer → ServiceInstance → Request Execution +---- + +1. The client uses a logical service name instead of a fixed URL. +2. The `DiscoveryClient` retrieves all available instances from Eureka. +3. Spring Cloud LoadBalancer selects one instance. +4. The request is sent to the selected instance. + +==== Example Usage + +[source,java] +---- +@Autowired +private RestTemplate restTemplate; + +public String callService() { + return restTemplate.getForObject("http://STORES/api", String.class); +} +---- + +In this example: +- `STORES` is resolved using Eureka. +- A service instance is selected using Spring Cloud LoadBalancer. + +==== Zone Preference + +In distributed systems, services are often deployed across multiple zones. + +Setting zone metadata helps LoadBalancer prefer instances in the same zone: + +---- +eureka.instance.metadataMap.zone=zone1 +---- + +This reduces latency and improves fault tolerance. + +==== Troubleshooting + +Common issues: + +*No instances found* +- Ensure the service is registered with Eureka. +- Verify `spring.application.name`. + +*Load balancing not working* +- Ensure Spring Cloud LoadBalancer dependency is present. + +*Incorrect zone selection* +- Check `metadataMap.zone` configuration. +- Ensure consistent zone naming. + +==== Best Practices + +- Always define `spring.application.name`. +- Use zone metadata in multi-zone deployments. +- Avoid hardcoding service URLs. +- Monitor logs for debugging load balancing behavior. + === AOT and Native Image Support Spring Cloud Netflix Eureka Client integration supports Spring AOT transformations and native images, however, only with refresh mode disabled. @@ -419,7 +486,6 @@ To include Eureka Server in your project, use the starter with a group ID of `or See the https://projects.spring.io/spring-cloud/[Spring Cloud Project page] for details on setting up your build system with the current Spring Cloud Release Train. NOTE: If your project already uses Thymeleaf as its template engine, the Freemarker templates of the Eureka server may not be loaded correctly. In this case it is necessary to configure the template loader manually: - .application.yml ---- spring: