spring-cloud / spring-cloud-netflix

Integration with Netflix OSS components
http://cloud.spring.io/spring-cloud-netflix/
Apache License 2.0
4.87k stars 2.44k forks source link

UnresolvedAddressException when using webClient.get() to get from a discovery eureka client #4269

Closed Ivanbattochio closed 6 months ago

Ivanbattochio commented 6 months ago

Hi, I'd like to know if anyone can help me.

I have a setup of microservices registering to a netflix discovery server, and I'm having problems using webClient to communicate with the servers using their instance names. When trying to request using RestTemplate it works as expected.

The entire project source code is inside this repo!

InventoryResponse[] inventoryResponses = webClient.get() .uri("http://inventory-service/api/inventory", uriBuilder -> uriBuilder.queryParam("skuCode", skuCodes).build()) .retrieve() .bodyToMono(InventoryResponse[].class) .block();

This is how I'm building and sending the request from the order-service to the inventory-service and this is the error stacktrace:

java.nio.channels.UnresolvedAddressException: null at java.base/sun.nio.ch.Net.checkAddress(Net.java:137) ~[na:na] at java.base/sun.nio.ch.Net.checkAddress(Net.java:145) ~[na:na] at java.base/sun.nio.ch.SocketChannelImpl.checkRemote(SocketChannelImpl.java:842) ~[na:na] at java.base/sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:865) ~[na:na] at java.net.http/jdk.internal.net.http.PlainHttpConnection.lambda$connectAsync$1(PlainHttpConnection.java:210) ~[java.net.http:na] at java.base/java.security.AccessController.doPrivileged(AccessController.java:571) ~[na:na] at java.net.http/jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:212) ~[java.net.http:na] at java.net.http/jdk.internal.net.http.Http1Exchange.sendHeadersAsync(Http1Exchange.java:312) ~[java.net.http:na] at java.net.http/jdk.internal.net.http.Exchange.lambda$responseAsyncImpl0$8(Exchange.java:567) ~[java.net.http:na] at java.net.http/jdk.internal.net.http.Exchange.checkFor407(Exchange.java:447) ~[java.net.http:na] at java.net.http/jdk.internal.net.http.Exchange.lambda$responseAsyncImpl0$9(Exchange.java:571) ~[java.net.http:na] at java.base/java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934) ~[na:na] at java.base/java.util.concurrent.CompletableFuture.uniHandleStage(CompletableFuture.java:950) ~[na:na] at java.base/java.util.concurrent.CompletableFuture.handle(CompletableFuture.java:2372) ~[na:na] at java.net.http/jdk.internal.net.http.Exchange.responseAsyncImpl0(Exchange.java:571) ~[java.net.http:na] at java.net.http/jdk.internal.net.http.Exchange.responseAsyncImpl(Exchange.java:423) ~[java.net.http:na] at java.net.http/jdk.internal.net.http.Exchange.responseAsync(Exchange.java:415) ~[java.net.http:na] at java.net.http/jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:413) ~[java.net.http:na] at java.net.http/jdk.internal.net.http.MultiExchange.lambda$responseAsyncImpl$7(MultiExchange.java:454) ~[java.net.http:na] at java.base/java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934) ~[na:na] at java.base/java.util.concurrent.CompletableFuture.uniHandleStage(CompletableFuture.java:950) ~[na:na] at java.base/java.util.concurrent.CompletableFuture.handle(CompletableFuture.java:2372) ~[na:na] at java.net.http/jdk.internal.net.http.MultiExchange.responseAsyncImpl(MultiExchange.java:444) ~[java.net.http:na] at java.net.http/jdk.internal.net.http.MultiExchange.lambda$responseAsync0$2(MultiExchange.java:346) ~[java.net.http:na] at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1150) ~[na:na] at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) ~[na:na] at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run$$$capture(CompletableFuture.java:1773) ~[na:na] at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java) ~[na:na] at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) ~[na:na] at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) ~[na:na] at java.base/java.lang.Thread.run(Thread.java:1583) ~[na:na]

This is the inventory-service application.properties

spring.datasource.driver-class-name=org.postgresql.Driver spring.datasource.url=jdbc:postgresql://localhost:5432/inventoryservice spring.datasource.username=postgres spring.datasource.password=admin spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect server.port=8082 eureka.client.service-url.default-zone=http://localhost:8761/eureka spring.application.name=inventory-service

This is the inventory-service build.gradle file

`plugins { id 'java' id 'org.springframework.boot' version '3.2.3' id 'io.spring.dependency-management' version '1.1.4' }

group = 'com.example.microservices' version = '0.0.1-SNAPSHOT'

java { sourceCompatibility = '21' }

ext { set('springCloudVersion', "2023.0.0") }

configurations { compileOnly { extendsFrom annotationProcessor } }

repositories { mavenCentral() }

dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' compileOnly 'org.projectlombok:lombok' runtimeOnly 'org.postgresql:postgresql' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' }

dependencyManagement { imports { mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" } }

tasks.named('test') { useJUnitPlatform() } `

This is the order-service application.properties

spring.datasource.driver-class-name=org.postgresql.Driver spring.datasource.url=jdbc:postgresql://localhost:5432/postgres spring.datasource.username=postgres spring.datasource.password=admin spring.jpa.hibernate.ddl-auto=update spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect server.port=8081 eureka.client.service-url.default-zone=http://localhost:8761/eureka spring.application.name=order-service

This is the order service build.gradle

`plugins { id 'java' id 'org.springframework.boot' version '3.2.3' id 'io.spring.dependency-management' version '1.1.4' }

group = 'com.example.springmicroservices' version = '0.0.1-SNAPSHOT'

java { sourceCompatibility = '21' }

ext { set('springCloudVersion', "2023.0.0") }

configurations { compileOnly { extendsFrom annotationProcessor } }

repositories { mavenCentral() }

dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' implementation 'org.springframework:spring-webflux' implementation 'jakarta.validation:jakarta.validation-api' compileOnly 'org.projectlombok:lombok' runtimeOnly 'org.postgresql:postgresql' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' }

dependencyManagement { imports { mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" } }

tasks.named('test') { useJUnitPlatform() } ` This is the discovery-service application.properties

spring.application.name=discovery-service eureka.instance.hostname=localhost eureka.client.register-with-eureka=false eureka.client.fetch-registry=false server.port=8761 eureka.client.service-url.default-zone=http://localhost:8761/eureka

This is the discovery-service build.gradle

`plugins { id 'java' id 'org.springframework.boot' version '3.2.3' id 'io.spring.dependency-management' version '1.1.4' }

group = 'com.example.microservices' version = '0.0.1-SNAPSHOT'

java { sourceCompatibility = '21' }

repositories { mavenCentral() }

ext { set('springCloudVersion', "2023.0.0") }

dependencies { implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server' testImplementation 'org.springframework.boot:spring-boot-starter-test' }

dependencyManagement { imports { mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" } }

tasks.named('test') { useJUnitPlatform() } `

I'm using Java 21 and spring boot 3.2.3.

When debbuging the request i found where the error gets thrown:

image

I've tried to provide all the information i think anyone would need to replicate the problem, if i let something slip by please let me know so that i can provide more context!

Thanks!

ZIRAKrezovic commented 6 months ago

You need to provide a WebClient.Builder bean and annotate it with @LoadBalanced or provide load balancer filter function to the web client. See https://spring.io/guides/gs/spring-cloud-loadbalancer

Code from my project:

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.web.reactive.function.client.WebClient;

    @Bean
    @Primary
    public WebClient.Builder webClientBuilder() {
        return WebClient.builder();
    }

    @Bean
    @LoadBalanced
    public WebClient.Builder loadBalancedWebClientBuilder() {
        return WebClient.builder();
    }
BenEfrati commented 4 months ago

I think this is a bug, adding @Primary is just a workaround. doing so will cause all spring auto configuration to not work as excepted (WebClientAutoConfiguration)

Ivanbattochio commented 4 months ago

This was how i solved the issue inside my project without @Primary, turns out that i was importing spring framework's webflux outside spring boot ecosystem.

I don't know if that indicates that there is or not a bug that needs to be fixed, I only wanted to provide more context about how my issue was solved.

BenEfrati commented 4 months ago

Hi, If your eureka client will use WebClient it will fail due to error of: No qualifying bean of type 'org.springframework.web.reactive.function.client.WebClient$Builder' available: expected single matching bean but found 2: webClientBuilder,loadBalancerWebClient

eureka:
  client:
    webclient:
      enabled: true