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.