Affects: 6.0.0-SNAPSHOT


Hi Spring Team,

Can you please add the ability to reference properties for @HttpExchange, @GetExchange, and @PostExchange defined in application.yaml?

Right now, I have to hard code my URL in the annotation:

@HttpExchange(url = "/multitenant/public/api")
public interface CumulusRepository {

    @GetExchange("/get_stuff")
    Cumulus getCumulusAlerts(@RequestHeader Map<String, String> headers);

    @PostExchange("/getAccessToken")
    Token getAccessToken(@RequestBody String credential);
}

But I wish to define my URL in application.yaml and reference it in the annotations like this:

@HttpExchange(url = "${cumulus.baseUrl}")
public interface CumulusRepository {

    @GetExchange("${cumulus.alertUrl}")
    Cumulus getCumulusAlerts(@RequestHeader Map<String, String> headers);

    @PostExchange("${cumulus.tokenUrl}")
    Token getAccessToken(@RequestBody String credential);
}

Comment From: jcthalys

This is still not working for me:

// using spring boot 3.0.0 and framework 6.0.2
@SpringBootApplication
public class HttpInterfaceApplication {

    public static void main(String[] args) {
        SpringApplication.run(HttpInterfaceApplication.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();
}

Error:

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.2.jar:6.0.2]
    at org.springframework.web.util.UriComponents.expandUriComponent(UriComponents.java:263) ~[spring-web-6.0.2.jar:6.0.2]
    at org.springframework.web.util.HierarchicalUriComponents$FullPathComponent.expand(HierarchicalUriComponents.java:921) ~[spring-web-6.0.2.jar:6.0.2]
    at org.springframework.web.util.HierarchicalUriComponents.expandInternal(HierarchicalUriComponents.java:439) ~[spring-web-6.0.2.jar:6.0.2]
    at org.springframework.web.util.HierarchicalUriComponents.expandInternal(HierarchicalUriComponents.java:52) ~[spring-web-6.0.2.jar:6.0.2]
    at org.springframework.web.util.UriComponents.expand(UriComponents.java:161) ~[spring-web-6.0.2.jar:6.0.2]
    at org.springframework.web.util.DefaultUriBuilderFactory$DefaultUriBuilder.build(DefaultUriBuilderFactory.java:391) ~[spring-web-6.0.2.jar:6.0.2]
    at org.springframework.web.util.DefaultUriBuilderFactory.expand(DefaultUriBuilderFactory.java:149) ~[spring-web-6.0.2.jar:6.0.2]
    at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultRequestBodyUriSpec.uri(DefaultWebClient.java:228) ~[spring-webflux-6.0.2.jar:6.0.2]
    at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultRequestBodyUriSpec.uri(DefaultWebClient.java:189) ~[spring-webflux-6.0.2.jar:6.0.2]
    at org.springframework.web.reactive.function.client.support.WebClientAdapter.newRequest(WebClientAdapter.java:105) ~[spring-webflux-6.0.2.jar:6.0.2]
    at org.springframework.web.reactive.function.client.support.WebClientAdapter.requestToBody(WebClientAdapter.java:69) ~[spring-webflux-6.0.2.jar:6.0.2]
    at org.springframework.web.service.invoker.HttpServiceMethod$ResponseFunction.lambda$initBodyFunction$5(HttpServiceMethod.java:378) ~[spring-web-6.0.2.jar:6.0.2]
    at org.springframework.web.service.invoker.HttpServiceMethod$ResponseFunction.execute(HttpServiceMethod.java:288) ~[spring-web-6.0.2.jar:6.0.2]
    at org.springframework.web.service.invoker.HttpServiceMethod.invoke(HttpServiceMethod.java:105) ~[spring-web-6.0.2.jar:6.0.2]
    at org.springframework.web.service.invoker.HttpServiceProxyFactory$HttpServiceMethodInterceptor.invoke(HttpServiceProxyFactory.java:271) ~[spring-web-6.0.2.jar:6.0.2]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.0.2.jar:6.0.2]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:218) ~[spring-aop-6.0.2.jar:6.0.2]

When I use the url directly it works fine

Comment From: renatomrcosta

This is still not working for me:

I stumbled in the same issue, and found out perhaps a clue to clarify if the URL field considers application properties at all, or just URI placeholders.

This error indicates that the application properties with placeholders are not considered at all: If you try, it just considers it a path: (like when you call "/v1/api/customer/{customerId}". The $ sign in java or \$ in Kotlin were not parsed.

@HttpExchange(url = "{httpbin}") // I chose a single name here, because if we try to simply remove the braces and add the $ back, it still doesn't work, but it's easy enough to use to test.
interface HttpBinClient {
    @GetExchange("/anything")
    fun anything(@PathVariable("httpbin") httpbin: String): Mono<String>
}

(Repo with working example here: LINK)

so, in this case: is it truly intended that the URL and other fields can be parameterized with application properties at all? If so, then this ticket shouldn't be closed as completed.

Otherwise @jcthalys , I recommend taking the more verbose approach of providing a webClient with its baseUrl set per httpProxyFactory you need to build for the time being, and not setting the URL property at the annotation level. It's the approach I'll be taking for the time being.