{"id":17902922,"url":"https://github.com/ilozano2/spring-cloud-gateway-active-health-check","last_synced_at":"2026-04-12T16:04:10.053Z","repository":{"id":179479932,"uuid":"597042432","full_name":"ilozano2/spring-cloud-gateway-active-health-check","owner":"ilozano2","description":"Blog post describing how to get Active Health Check using Spring Cloud Gateway","archived":false,"fork":false,"pushed_at":"2023-07-07T14:18:31.000Z","size":111,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-08T19:42:45.482Z","etag":null,"topics":["health-check","spring","spring-cloud-gateway"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ilozano2.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-02-03T13:57:32.000Z","updated_at":"2023-11-15T07:44:03.000Z","dependencies_parsed_at":null,"dependency_job_id":"05c0c97b-0568-485b-b680-ac06472b3091","html_url":"https://github.com/ilozano2/spring-cloud-gateway-active-health-check","commit_stats":null,"previous_names":["ilozano2/spring-cloud-gateway-active-health-check"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ilozano2%2Fspring-cloud-gateway-active-health-check","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ilozano2%2Fspring-cloud-gateway-active-health-check/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ilozano2%2Fspring-cloud-gateway-active-health-check/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ilozano2%2Fspring-cloud-gateway-active-health-check/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ilozano2","download_url":"https://codeload.github.com/ilozano2/spring-cloud-gateway-active-health-check/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246944385,"owners_count":20858773,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["health-check","spring","spring-cloud-gateway"],"created_at":"2024-10-28T16:38:37.300Z","updated_at":"2026-04-12T16:04:10.010Z","avatar_url":"https://github.com/ilozano2.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Active health check strategies with Spring Cloud Gateway\n\nNowadays, applications are built as a collection of small independent upstream services. This accelerates development and allows modules to be focused on specific responsibilities, increasing their quality. This is one of the main advantages of using a microservice approach. However, jumping from one service to another can add extra latency, and this latency can be dramatically higher when the services are not responding.\n\nIf you run microservices, you want to prevent your upstream services from being called when they are not working properly. Even using a circuit breaker pattern can also generate a penalty in the response time. For this reason, it is sometimes better to actively check your upstream services to verify they are ready before they are needed.\n\n\u003eA health check is a way to determine if a service can respond correctly according to its status, preventing timeouts and errors.\n\u003e\n\u003e **Passive health check** is done during request handling. If the service is finally unhealthy, the application will return a failure marking the endpoint unhealthy. It can add extra latency.\n\u003e\n\u003e **Active health check** will check and drop unhealthy services in the background before receiving the request. It doesn't add extra latency.\n\nLast but not least, these features can be combined with a circuit breaker library to immediately fall back on an alternative endpoint without suffering the first miss penalty.\n\nThe goal is for routes to forward the requests to upstream services that are healthy by using a load balancer strategy:\n\n![Active health check Diagram](active-hc-diagram.png)\n\nThis post is divided into two parts:\n1. \"Spring features you need\" - describing which Spring’s features you need to get active health check.\n2. \"Registering endpoints for your services\" - visiting some approaches for adding one or more endpoints to your routes.\n\n# 1. Spring features you need\n\nThere are some features in Spring that can help you to get active health check\n\n* **Spring Cloud Load Balancer** (SLB) is a client-side load-balancer that allows balancing traffic between different upstream service endpoints. It is part of [Spring Cloud project](https://spring.io/projects/spring-cloud), and is included in the spring-cloud-commons library (see the [SLB documentation](https://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#spring-cloud-loadbalancer)).\n* The client-side service discovery feature lets the client find and communicate with services without hard-coding the hostname and port. It is also included in the spring-cloud-commons library (see the [Service Discovery documentation](https://docs.spring.io/spring-cloud-commons/docs/current/reference/html/#discovery-client)).\n\n**Spring Cloud Gateway**\n[Spring Cloud Gateway](https://cloud.spring.io/spring-cloud-gateway) provides a library for building API gateways on top of Spring and Java.\nIt supports the above features through the [LoadBalancerClientFilter](https://cloud.spring.io/spring-cloud-gateway/2.1.x/multi/multi__global_filters.html#_loadbalancerclient_filter)/[ReactiveLoadBalancerClientFilter](https://cloud.spring.io/spring-cloud-gateway/2.1.x/multi/multi__global_filters.html#reactive-loadbalancer-client-filter) global filters.\nIn this post, you can see different ways to use one of those global filters.\n\nFirst, though, let’s explore some of those features.\n\n### Spring Cloud Load Balancer filter\n\nA global filter for load balancing is included in Spring Cloud and can be activated by using a special URI notation: `lb://your-service-name`.\n\n```yaml\nspring:\n cloud:\n   gateway:\n     routes:\n       - id: myRoute\n         uri: lb://your-service-name\n         predicates:\n         - Path=/service/**\n```\n\nThe load balancer filter, [ReactiveLoadBalancerClientFilter](https://cloud.spring.io/spring-cloud-gateway/2.1.x/multi/multi__global_filters.html#reactive-loadbalancer-client-filter) (for reactive applications), will detect the URI and replace it with an available endpoint associated with \"your-service-name\".\n\nTake into account that you need to register \"your-service-name\" in the Service Discovery registry. We will see different ways you can do it in the following sections.\n\n### Active health check\n\nBy default, traffic is routed to upstream services, even if they are unhealthy. \nTo prevent picking a bad one, you can enable the `health-check` configuration provided by the Load Balancer Client from Spring Cloud:\n\n```yaml\n    spring:\n      cloud:  \n        loadbalancer:  \n          configurations: health-check\n```\nAll the endpoints will be checked periodically by automatically using the Spring Boot Actuator health endpoint. \nYou can also customize some options like `spring.cloud.loadbalancer.health-check.\u003cyour-service-name\u003e.path` and `spring.cloud.loadbalancer.health-check.interval`.\n\n\u003e The default health check configuration checks the upstream service endpoints by using the `/actuator/health` endpoint, which requires activating Spring Actuator in your upstream service.\n\nFor more options, explore the [LoadBalancerClientsProperties](https://github.com/spring-cloud/spring-cloud-commons/blob/main/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerClientsProperties.java) and [LoadBalancerProperties](https://github.com/spring-cloud/spring-cloud-commons/blob/main/spring-cloud-commons/src/main/java/org/springframework/cloud/client/loadbalancer/LoadBalancerProperties.java) classes\n\n\u003e There is a built-in feature in Spring Cloud Gateway that will deploy all the services available as routes. This post describes the opposite, so we are declaring routes that are load balanced, including active health check.\n\n# 2. Registering endpoints for your services\n\nIn the previous section, you specified a load-balanced URI (`lb://your-service-name`), but now you need to register the endpoints associated with the service name of the URI. \nWe are visiting some approaches in the following sections.\n\n## Static approach\n\nYou can statically activate client load balancing by configuring the `spring.cloud.discovery.client.simple.instances` property.\nIt is a map whose key is the service name (used by the lb:// URI) and the value is an array of `org.springframework.cloud.client.ServiceInstance` objects that point to the upstream services.\n\nSome benefits of static load balancing include:\n* Load balancing could distribute traffic between multiple instances, sharing any stress of the services and reducing the probability of crashing.\n* Fault tolerance.\n\nThe problem is that you are statically setting the upstream services in your configuration. If you need to change the list, you need to restart your application.\n\nExample:\n```yaml\nspring:\n  cloud:\n    gateway:\n      routes:\n        - uri: lb://hello-service # Load Balancer URI handled by ReactiveLoadBalancerClientFilter\n          predicates:\n            - Path=/hello\n    loadbalancer:\n      configurations: health-check # Required for enabling SDC with health checks\n    discovery:\n      client:\n        simple: # SimpleDiscoveryClient to configure statically services\n          instances:\n            hello-service:\n              - secure: false\n                port: 8090\n                host: localhost\n                serviceId: hello-service\n                instanceId: hello-service-1\n              - secure: false\n                port: 8091\n                host: localhost\n                serviceId: hello-service\n                instanceId: hello-service-2\n```\n\n### Trying out\n\n1. Run servers\n```shell\n# Run server 1\nSERVER_PORT=8090 ./gradlew :service:bootRun\n```\n```shell\n# Run server 2\nSERVER_PORT=8091 ./gradlew :service:bootRun\n```\n\n2. Check http://localhost:8090/actuator/health is \"UP\"\n\n```shell\ncurl http://localhost:8090/actuator/health\n```\n\n```\n {\"status\":\"UP\"}\n```\n\n3. Test http://localhost:8080/hello responds 200 OK\n\n```shell\ncurl localhost:8090/hello\n```\n\n```\n{ \"message\": \"hello world!\"}%\n```\n\n4. Run Spring Cloud Gateway\n\n```shell\n./gradlew :1-service-disc-by-properties:bootRun\n```\n\n5. Test Spring Cloud Gateway balancer\n\n```shell\ncurl localhost:8881/hello\n```\n\n```\n{ \"message\": \"hello world from port 8090!\"}%\n```\n\n```shell\ncurl localhost:8881/hello\n```\n\n```\n{ \"message\": \"hello world from port 8091!\"}%\n```\n\nYou could need to run multiple times the previous commands to get a response from a different server.\n\n6. Mark server 1 as unhealthy sending PUT request to http://localhost:8090/status/false\n\n```shell\ncurl localhost:8090/status/false -X PUT\n```\n\n7. Check http://localhost:8090/actuator/status is \"DOWN\"\n\n```shell\ncurl http://localhost:8090/actuator/health\n```\n\n```\n{\"status\":\"DOWN\"}\n```\n\n8. Run multiple times a GET request to http://localhost:8881/hello and see that you only get response from port 8091\n\nYou could receive one response on port 8090 owing the healthcheck haven't checked the endpoint when you send the request. \nThe interval can be modified in the property spring.cloud.loadbalancer.health-check.interval `spring.cloud.loadbalancer.health-check.interval`\n\nAlso, you can see some messages that describe one of the upstream endpoints as not healthy, and therefore, it is unavailable.\n\n```\n2023-05-08 14:59:53.151 DEBUG 9906 --- [ctor-http-nio-3] r.n.http.client.HttpClientOperations     : [12d42e83-77, L:/127.0.0.1:57439 - R:localhost/127.0.0.1:8090] Received response (auto-read:false) : RESPONSE(decodeResult: success, version: HTTP/1.1)\nHTTP/1.1 503 Service Unavailable\n```\n\n```shell\ncurl localhost:8881/hello\n```\n\n```\n{ \"message\": \"hello world from port 8091!\"}%\n```\n\n9. Mark server 2 as unhealthy sending PUT request to http://localhost:8091/status/false\n\n```shell\ncurl localhost:8091/status/false -X PUT\n```\n\n10. Run some GET requests to http://localhost:8881/hello and see that it responds \"503 Service Unavailable\"\n\n```shell\ncurl localhost:8881/hello\n```\n\n```\n{\"timestamp\":\"2023-05-08T13:07:48.704+00:00\",\"path\":\"/hello\",\"status\":503,\"error\":\"Service Unavailable\",\"requestId\":\"6b5d6010-199\"}%\n```\n\n11. Stop all the servers started in the previous steps\n\n## Eureka integration (+complex, dynamic)\n\nHaving a static configuration is not very flexible, but using Eureka as a service discovery can remove that drawback.\n\nThe cost is that you require a new component in your architecture which can increase your maintenance burden. This might not be an option for some clients.\n\nThe following example configures Eureka integration:\n\n```yaml\n    spring:\n      application:\n        name: scg-client-with-eureka\n      cloud:\n        loadbalancer:\n          configurations: health-check # Note: required for enabling SDC with health checks - remove this line if you want to reproduce issues because not using health checks in LB\n          # Note: LoadBalancerCacheProperties.ttl (or spring.cloud.loadbalancer.cache.ttl) is 35 by default - You will need to wait 35secs after an instance turns healthy\n        gateway:\n          httpclient:\n            wiretap: true\n          routes:\n            - uri: lb://hello-service\n              predicates:\n                - Path=/headers\n              filters:\n                - StripPrefix=0\n    \n    eureka:\n      client:\n        webclient:\n          enabled: true\n        serviceUrl:\n          defaultZone: http://localhost:8761/eureka\n        fetchRegistry: true\n        registerWithEureka: false\n      instance:\n        preferIpAddress: true\n```\n\n### Trying out\n\n1. Run Eureka Server\n```shell\n./gradlew :eureka-server:bootRun\n```\n\nWait until you can see Eureka server was started up\n\n```\n2023-06-26 12:51:46.901  INFO 88601 --- [       Thread-9] e.s.EurekaServerInitializerConfiguration : Started Eureka Server\n```\n\n2. Run servers including `eureka` profile\n```shell\n# Run server 1\nSPRING_PROFILES_ACTIVE=eureka SERVER_PORT=8090 ./gradlew :service:bootRun\n```\n```shell\n# Run server 2\nSPRING_PROFILES_ACTIVE=eureka SERVER_PORT=8091 ./gradlew :service:bootRun\n```\n\nYou should see that the sever instances were added into Eureka in the servers' logs from step 1.\n\n```\n2023-06-26 12:52:50.805  INFO 88601 --- [nio-8761-exec-3] c.n.e.registry.AbstractInstanceRegistry  : Registered instance HELLO-SERVICE/192.168.0.14:hello-service:8090 with status UP (replication=true)\n2023-06-26 12:53:29.127  INFO 88601 --- [nio-8761-exec-9] c.n.e.registry.AbstractInstanceRegistry  : Registered instance HELLO-SERVICE/192.168.0.14:hello-service:8091 with status UP (replication=true)\n```\n\n3. Go to http://localhost:8761/ and check the servers are included  as instance of the application `hello-service`\n\n4. Run Spring Cloud Gateway\n\n```shell\nSERVER_PORT=8883 ./gradlew :3-eureka-service-disc:bootRun\n```\n\n5.Test Spring Cloud Gateway balancer\n\n```shell\ncurl localhost:8883/hello\n```\n\n```\n{ \"message\": \"hello world from port 8090!\"}%\n```\n\n```shell\ncurl localhost:8883/hello\n```\n\n```\n{ \"message\": \"hello world from port 8091!\"}%\n```\n\n6. Mark server 1 as unhealthy sending PUT request to http://localhost:8090/status/false\n\n```shell\ncurl localhost:8090/status/false -X PUT\n```\n\nYou should see in the Eureka dashboard that there is only one instance available, and you will see some logs messages complaining that service on port `8090` is not available.\nThe health check is not immediate, so you might need to wait a few seconds to see the instance marked as DOWN.\n\n7. Stop all the servers started in the previous steps\n\n## Custom Filter at Route level (dynamic approach)\n\nAs you have seen, Spring Cloud Gateway offers an option for creating your own custom filters. It also lets you apply filters and change routes without restarting your gateway.\n\nIn this section, you can see a custom filter implementation that sets up load balancing and health checks of your services by using Spring Cloud Gateway route configuration.\n\nIf you already have a service discovery server in your project this might not be your best option. If not, this is a simple and cheap way to integrate two great features in your project.\n\n```yaml\n    spring:\n      application:\n        name: custom-service-disc\n      cloud:\n        loadbalancer:\n          configurations: health-check # Note: required for enabling SDC with health checks - remove this line if you want to reproduce issues because not using health checks in LB\n          # Note: LoadBalancerCacheProperties.ttl (or spring.cloud.loadbalancer.cache.ttl) is 35 by default - You will need to wait 35secs after an instance turns healthy\n        gateway:\n          routes:\n            - uri: lb://hello-service\n              id: load-balanced\n              predicates:\n                - Path=/load-balanced/**\n              filters:\n                - StripPrefix=1\n                - LoadBalancer=localhost:8090;localhost:8091;localhost:8092\n```\n\nThe new `LoadBalancer` route filter lets you configure the upstream service endpoints associated with the `lb://hello-service` load balancer URI:\n\n```\n@Component\npublic class LoadBalancerGatewayFilterFactory extends AbstractGatewayFilterFactory\u003cLoadBalancerGatewayFilterFactory.MyConfiguration\u003e {\n\n\t// ...\n\n\t@Override\n\tpublic GatewayFilter apply(MyConfiguration config) {\n\t\treturn (exchange, chain) -\u003e {\n\t\t\tfinal Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);\n\t\t\tif (StringUtils.hasText(config.getInstances()) \u0026\u0026 route.getUri().getScheme().equals(\"lb\")) {\n\t\t\t\tconfig.getServiceInstances(route.getUri().getHost()).forEach(discoveryClient::addInstance);\n\t\t\t}\n\n\t\t\treturn chain.filter(exchange);\n\t\t};\n\t}\n\n```\n\nIf a route matches the `lb://\u003cservice-host\u003e` pattern, the `LoadBalancerGatewayFilterFactory` will associate all the upstream service endpoints coming from the filter configuration to the `service-host`.\n\nUnder the hood, a new `ReactiveCustomDiscoveryClient` discovery client implementation has been included to manage upstream service endpoints in our code. \nSpring detects such a bean and prioritizes it in the list of [DiscoveryClient](https://github.com/spring-cloud/spring-cloud-commons/blob/main/spring-cloud-commons/src/main/java/org/springframework/cloud/client/discovery/DiscoveryClient.java) used to determine available endpoints.\n\n### Trying out\n\n1. Run servers\n```shell\n# Run server 1\nSERVER_PORT=8090 ./gradlew :service:bootRun\n```\n```shell\n# Run server 2\nSERVER_PORT=8091 ./gradlew :service:bootRun\n```\n\n2. Check http://localhost:8090/actuator/health is \"UP\"\n\n```shell\ncurl http://localhost:8090/actuator/health\n```\n\n```\n{\"status\":\"UP\"}\n```\n\n3. Test http://localhost:8080/hello responds 200 OK\n\n```shell\ncurl localhost:8090/hello\n```\n\n```\n{ \"message\": \"hello world!\"}%\n```\n\n4. Run Spring Cloud Gateway\n\n```shell\nSERVER_PORT=8882 ./gradlew :2-custom-service-disc:bootRun\n```\n\n5. Test Spring Cloud Gateway balancer\n\n```shell\ncurl localhost:8882/hello\n```\n\n```\n{ \"message\": \"hello world from port 8090!\"}%\n```\n\n```shell\ncurl localhost:8882/hello\n```\n\n```\n{ \"message\": \"hello world from port 8091!\"}%\n```\n\nYou could need to run multiple times the previous commands to get a response from a different server.\n\n6. Mark server 1 as unhealthy sending PUT request to http://localhost:8090/status/false\n\n```shell\ncurl localhost:8090/status/false -X PUT\n```\n\n7. Check http://localhost:8090/actuator/status is \"DOWN\"\n\n```shell\ncurl http://localhost:8090/actuator/health\n```\n\n```\n{\"status\":\"DOWN\"}\n```\n\n8. Run multiple times a GET request to http://localhost:8881/hello and see that you only gets responds from port 8091\n\nYou could receive one response on port `8090` owing to the healthcheck not having checked the endpoint when you send the request. \nThe interval can be modified in the `spring.cloud.loadbalancer.health-check.interval` property.\n\nAlso, you can see some messages that describe one of the upstream endpoints as not healthy, and, therefore, it is unavailable.\n\n```\n2023-05-08 15:59:53.151 DEBUG 9906 --- [ctor-http-nio-2] r.n.http.client.HttpClientOperations     : [12d42e83-77, L:/127.0.0.1:57439 - R:localhost/127.0.0.1:8090] Received response (auto-read:false) : RESPONSE(decodeResult: success, version: HTTP/1.1)\nHTTP/1.1 503 Service Unavailable\n```\n\n```shell\ncurl localhost:8882/hello\n```\n\n```\n{ \"message\": \"hello world from port 8091!\"}%\n```\n\n9. Mark server 2 as unhealthy sending PUT request to http://localhost:8091/status/false\n\n```shell\ncurl localhost:8091/status/false -X PUT\n```\n\n10. Run some GET requests to http://localhost:8881/hello and see that it responds \"503 Service Unavailable\"\n\n```shell\ncurl localhost:8882/hello\n```\n\n```\n{\"timestamp\":\"2023-05-08T14:07:48.704+00:00\",\"path\":\"/hello\",\"status\":503,\"error\":\"Service Unavailable\",\"requestId\":\"6b5d6010-199\"}%\n```\n\n11. Stop all the servers started in the previous steps\n\n### Next steps\n\nIn this post, you have seen multiple ways to get load balancing and active health checks in your projects.\n* From the static approach for basic projects or proof of concepts where the number of upstream services doesn’t change.\n* As a more dynamic approach, using Eureka or Spring Cloud Gateway filters.\n\nTo sum up, you have also seen that the Spring Cloud Gateway approach is a great option if you do not need to add an extra component to your architecture.\n\n# Additional Resources\n\nWant to learn more about Spring Cloud? Join us virtually at [Spring Academy](https://spring.academy)! \nWant to get **active health check** just by adding a property in your route without getting your hands dirty? \nTake a look at our [commercial platform with Kubernetes](https://docs.vmware.com/en/VMware-Spring-Cloud-Gateway-for-Kubernetes/index.html) support.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Filozano2%2Fspring-cloud-gateway-active-health-check","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Filozano2%2Fspring-cloud-gateway-active-health-check","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Filozano2%2Fspring-cloud-gateway-active-health-check/lists"}