I suggest adding support for CompletableFuture and CompletionStage as a valid return type for methods in Spring client interfaces backed by RestClient and RestClientAdapter.

It would be great to provide the ExecutorService when creating the adapter or maybe leverage Async annotation

Example

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;
import org.springframework.web.service.annotation.GetExchange;
import org.springframework.web.service.annotation.PostExchange;
import org.springframework.web.service.annotation.RequestBody;
import org.springframework.web.service.annotation.PathVariable;
import org.springframework.http.ResponseEntity;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;

public interface MyApiClient {

    @GetExchange("/users/{id}")
    CompletableFuture<User> getUser(@PathVariable int id);

    @PostExchange("/users")
    CompletableFuture<ResponseEntity<User>> createUser(@RequestBody User user);

    @GetExchange("/items")
    CompletableFuture<List<Item>> getAllItems();
}

@Configuration
public class ClientConfig {

    @Bean
    public MyApiClient myApiClient(RestClient.Builder restClientBuilder) {
        RestClient restClient = restClientBuilder.baseUrl("https://api.example.com").build();
        HttpServiceProxyFactory factory = HttpServiceProxyFactory.builder(RestClientAdapter.forClient(restClient)).build();
        return factory.createClient(MyApiClient.class);
    }
}

@Service
public class MyService {

    private final MyApiClient apiClient;

    public MyService(MyApiClient apiClient) {
        this.apiClient = apiClient;
    }

    public void processUser(int userId) {
        apiClient.getUser(userId)
                 .thenAccept(user -> System.out.println("Retrieved user: " + user))
                 .exceptionally(ex -> {
                     System.err.println("Error fetching user: " + ex.getMessage());
                     return null;
                 });
    }
}

Comment From: bclozel

I am wondering whether we should even consider this. Applications are meant to use several HTTP interface clients and often scatter/gather requests to different services.

Futures and Executor services are quite limited in that space because: they don't propagate cancellation signals, they are harder to instrument for observability, harder to debug, it is harder to synchronize separate calls.

Java is bringing structured concurrency as a new preview in Java 25 and this is a very exciting development. As always, the Spring team will do its best to support a wide range of Java versions and make the adoption of new features as easy as possible. Structured concurrency will solve all problems listed above. Supporting Futures at this level would go against a massive trend in the Java language and would send the wrong signal to the community.

I think that if an application is not able to leverage structured concurrency, developers should consider wrapping the execution for async usage. This would certainly not solve all issues listed above, but again only structured concurrency is very promising in that space.

I am proposing to decline this enhancement request and instead document structured concurrency usage in our reference documentation when we feel it is the right time.