spring-cloud/spring-cloud-netflix

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

Ivanbattochio opened this issue · 4 comments

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!

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();
    }

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)

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.

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