Affects: 6.0.4 Spring Boot: 3.0.1


@SpringBootApplication
public class DemoExchangeApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoExchangeApplication.class, args);
    }
    @Bean
    ApplicationListener<ApplicationReadyEvent> ready(GoogleClient googleClient) {
        return event -> {
            String googlePage = googleClient.getGooglePage();
            System.out.println(googlePage);
        };
    }
    @Bean
    GoogleClient googleClient(HttpServiceProxyFactory factory) {
        return factory.createClient(GoogleClient.class);
    }
    @Bean
    HttpServiceProxyFactory getHttpServiceProxyFactory(WebClient.Builder builder) {
        return HttpServiceProxyFactory.builder()
                .clientAdapter(WebClientAdapter.forClient(builder.build())).build();
    }
}

@HttpExchange(url = "${google.url}")
interface GoogleClient {
    @GetExchange
    String getGooglePage();
}

If I have the url = "www.google.com" directly it work just fine, but using the property placeholder it's not getting resolved from the application.properties or yml..

java.lang.IllegalArgumentException: Map has no value for 'google.url'
    at org.springframework.web.util.UriComponents$MapTemplateVariables.getValue(UriComponents.java:348) ~[spring-web-6.0.4.jar:6.0.4]
    at org.springframework.web.util.UriComponents.expandUriComponent(UriComponents.java:263) ~[spring-web-6.0.4.jar:6.0.4]
    at org.springframework.web.util.HierarchicalUriComponents$FullPathComponent.expand(HierarchicalUriComponents.java:921) ~[spring-web-6.0.4.jar:6.0.4]
    at org.springframework.web.util.HierarchicalUriComponents.expandInternal(HierarchicalUriComponents.java:439) ~[spring-web-6.0.4.jar:6.0.4]
    at org.springframework.web.util.HierarchicalUriComponents.expandInternal(HierarchicalUriComponents.java:52) ~[spring-web-6.0.4.jar:6.0.4]
    at org.springframework.web.util.UriComponents.expand(UriComponents.java:161) ~[spring-web-6.0.4.jar:6.0.4]
    at org.springframework.web.util.DefaultUriBuilderFactory$DefaultUriBuilder.build(DefaultUriBuilderFactory.java:391) ~[spring-web-6.0.4.jar:6.0.4]
    at org.springframework.web.util.DefaultUriBuilderFactory.expand(DefaultUriBuilderFactory.java:149) ~[spring-web-6.0.4.jar:6.0.4]
    at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultRequestBodyUriSpec.uri(DefaultWebClient.java:228) ~[spring-webflux-6.0.4.jar:6.0.4]
    at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultRequestBodyUriSpec.uri(DefaultWebClient.java:189) ~[spring-webflux-6.0.4.jar:6.0.4]
    at org.springframework.web.reactive.function.client.support.WebClientAdapter.newRequest(WebClientAdapter.java:105) ~[spring-webflux-6.0.4.jar:6.0.4]
    at org.springframework.web.reactive.function.client.support.WebClientAdapter.requestToBody(WebClientAdapter.java:69) ~[spring-webflux-6.0.4.jar:6.0.4]
    at org.springframework.web.service.invoker.HttpServiceMethod$ResponseFunction.lambda$initBodyFunction$5(HttpServiceMethod.java:378) ~[spring-web-6.0.4.jar:6.0.4]
    at org.springframework.web.service.invoker.HttpServiceMethod$ResponseFunction.execute(HttpServiceMethod.java:288) ~[spring-web-6.0.4.jar:6.0.4]
    at org.springframework.web.service.invoker.HttpServiceMethod.invoke(HttpServiceMethod.java:105) ~[spring-web-6.0.4.jar:6.0.4]
    at org.springframework.web.service.invoker.HttpServiceProxyFactory$HttpServiceMethodInterceptor.invoke(HttpServiceProxyFactory.java:271) ~[spring-web-6.0.4.jar:6.0.4]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.0.4.jar:6.0.4]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:218) ~[spring-aop-6.0.4.jar:6.0.4]
    at com.example.demoexchange.$Proxy49.getGooglePage(Unknown Source) ~[na:na]
    at com.example.demoexchange.DemoExchangeApplication.lambda$ready$0(DemoExchangeApplication.java:24) ~[main/:na]
    at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:176) ~[spring-context-6.0.4.jar:6.0.4]
    at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:169) ~[spring-context-6.0.4.jar:6.0.4]
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:143) ~[spring-context-6.0.4.jar:6.0.4]
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:413) ~[spring-context-6.0.4.jar:6.0.4]
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:370) ~[spring-context-6.0.4.jar:6.0.4]
    at org.springframework.boot.context.event.EventPublishingRunListener.ready(EventPublishingRunListener.java:109) ~[spring-boot-3.0.1.jar:3.0.1]
    at org.springframework.boot.SpringApplicationRunListeners.lambda$ready$6(SpringApplicationRunListeners.java:80) ~[spring-boot-3.0.1.jar:3.0.1]
    at java.base/java.lang.Iterable.forEach(Iterable.java:75) ~[na:na]
    at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:118) ~[spring-boot-3.0.1.jar:3.0.1]
    at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:112) ~[spring-boot-3.0.1.jar:3.0.1]
    at org.springframework.boot.SpringApplicationRunListeners.ready(SpringApplicationRunListeners.java:80) ~[spring-boot-3.0.1.jar:3.0.1]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:327) ~[spring-boot-3.0.1.jar:3.0.1]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1302) ~[spring-boot-3.0.1.jar:3.0.1]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1291) ~[spring-boot-3.0.1.jar:3.0.1]
    at com.example.demoexchange.DemoExchangeApplication.main(DemoExchangeApplication.java:18) ~[main/:na]

Comment From: DanielLiu1123

You can pass a StringValueResolver to support this feature. ```java @Bean HttpServiceProxyFactory getHttpServiceProxyFactory(WebClient.Builder builder, Environment env) { return HttpServiceProxyFactory.builder() .embeddedValueResolver(env::resolvePlaceholders) .clientAdapter(WebClientAdapter.forClient(builder.build())).build(); }

Comment From: rstoyanchev

@DanielLiu1123 is correct. You need to give the resolver for placeholders to HttpServiceProxyFactory. When https://github.com/spring-projects/spring-boot/issues/31337 is resolved, you should be able to inject a pre-configured HttpServiceProxyFactory.Builder and this will become more convenient.